chore: 搭建项目骨架

- pyproject:hatchling + src 布局,核心依赖 psycopg/pydantic/pyyaml/cel-python
- FastAPI 归入 [api] 可选依赖,开发依赖走 PEP 735 dependency-groups
- 建立分层目录:ontology/compiler/storage/repository/graph/timeseries/expr/rules/api
- errors.py 统一异常基类;py.typed 标记类型完整
- ruff(100) + pytest 配置;冒烟测试校验核心包不引入 fastapi/uvicorn
This commit is contained in:
Donny committed 2026-11-05 02:18:10 +08:00
1 parent ecd5925b2c
commit ace505cc6b
16 files changed
+1536 -3

No files matched your search

+10 -3
View File
@@ -230,13 +230,20 @@ from adapter_python_pg import Adapter, ConstraintViolation
adapter = Adapter.load("ontology/factory-twin.yaml", dsn="postgresql://...")
repo = adapter.repository("Device") # 实体仓储
repo.save(obj); repo.get(id); repo.find(**where); repo.patch(id, patch); repo.delete(id)
repo.save(obj)
repo.get(id)
repo.find(**where)
repo.patch(id, patch)
repo.delete(id)
graph = adapter.graph() # 图操作
graph.link(src, "mounted_on", dst, since=...); graph.traverse(...); graph.path(...)
graph.link(src, "mounted_on", dst, since=...)
graph.traverse(...)
graph.path(...)
ts = adapter.timeseries("SensorReading") # 时序
ts.write(rows); ts.query(bucket="5m", agg="avg", **filters)
ts.write(rows)
ts.query(bucket="5m", agg="avg", **filters)
adapter.invoke("startLine", on="L-01", input={"deviceIds": ["D-0001"]}) # Actions
adapter.functions("Line")["utilization"] # Functions
+65
View File
@@ -0,0 +1,65 @@
[project]
name = "adapter-python-pg"
version = "0.1.0"
description = "基于 ontology 数据基座的 PostgreSQL 中间层适配器(FastAPI 项目以 pip 包引用)"
readme = "README.md"
requires-python = ">=3.11"
license = { text = "Proprietary" }
dependencies = [
"psycopg[binary,pool]>=3.2",
"pydantic>=2.9",
"pydantic-settings>=2.6",
"pyyaml>=6.0.2",
"cel-python>=0.2",
]
[project.optional-dependencies]
# FastAPI 只是可选依赖:核心包不 import 它,宿主项目才需要
api = ["fastapi>=0.115"]
# 开发依赖走 PEP 735 依赖组:uv sync 默认安装,不需要 --extra dev
[dependency-groups]
dev = [
"pytest>=8.0",
"pytest-asyncio>=0.24",
"testcontainers[postgres]>=4.8",
"ruff>=0.8",
"mypy>=1.13",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["src/adapter_python_pg"]
[tool.uv]
package = true
[tool.ruff]
line-length = 100
target-version = "py311"
src = ["src", "tests"]
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
# E501: 行宽交给 formatter
# RUF001/002/003: 中文文档与注释里的全角标点是刻意的,不视为歧义字符
ignore = ["E501", "RUF001", "RUF002", "RUF003"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
markers = [
"integration: 需要真实 PostgreSQL 的测试(testcontainers)",
]
[tool.mypy]
python_version = "3.11"
files = ["src"]
strict = true
warn_unused_ignores = true
disallow_untyped_defs = true
+28
View File
@@ -0,0 +1,28 @@
"""adapter_python_pg — ontology 驱动的 PostgreSQL 中间层适配器。
核心包**不导入** FastAPI;路由能力在 `adapter_python_pg.api`,且需要安装 `[api]` extra。
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any
__version__ = "0.1.0"
__all__ = ["Adapter", "__version__", "build_router"]
if TYPE_CHECKING: # pragma: no cover - 仅类型检查期可见
from adapter_python_pg.api.router import build_router
from adapter_python_pg.core import Adapter
def __getattr__(name: str) -> Any:
"""延迟导入:`import adapter_python_pg` 不应拉起 psycopg / fastapi 之外的任何子系统。"""
if name == "Adapter":
from adapter_python_pg.core import Adapter
return Adapter
if name == "build_router":
from adapter_python_pg.api.router import build_router
return build_router
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
+1
View File
@@ -0,0 +1 @@
"""FastAPI router 工厂(可选依赖,核心包不得导入本模块)。"""
@@ -0,0 +1 @@
"""本体 → 存储映射:幂等 DDL、索引推导、版本记录。"""
+35
View File
@@ -0,0 +1,35 @@
"""统一错误模型。
约定:所有对外抛出的异常都继承 `AdapterError`,宿主只需 catch 一个基类;
`ConstraintViolation` / `OntologyError` 属于调用方可恢复的输入类错误。
"""
from __future__ import annotations
class AdapterError(Exception):
"""本适配器所有异常的基类。"""
class OntologyError(AdapterError):
"""本体 YAML 不合法:缺字段、类型不符、引用悬空。"""
class ConstraintViolation(AdapterError):
"""写入违反本体里的 constraints。"""
class EntityNotFound(AdapterError):
"""按 id 取实体时不存在。"""
class QueryError(AdapterError):
"""查询构造或执行失败(SQL / Cypher)。"""
class UnsafeIdentifier(QueryError):
"""标识符未通过白名单校验——通常是注入尝试或本体里写了非法名字。"""
class Configuration(AdapterError):
"""连接、扩展缺失、版本不匹配等环境问题。"""
+1
View File
@@ -0,0 +1 @@
"""表达式双轨:SQL 下推编译 + CEL 求值。"""
+1
View File
@@ -0,0 +1 @@
"""Apache AGE 封装:建图、建边、遍历、路径;转义与白名单集中于此。"""
@@ -0,0 +1 @@
"""本体的解析与校验:YAML → pydantic spec。"""
View File
Whitespace-only changes.
@@ -0,0 +1 @@
"""实体读写、批量、部分更新、约束校验。"""
+1
View File
@@ -0,0 +1 @@
"""派生链接(传递闭包)、派生属性、计算属性。"""
@@ -0,0 +1 @@
"""连接池、事务边界、动态 SQL 构建(标识符管控)。"""
@@ -0,0 +1 @@
"""hypertable 读写、COPY 批量、聚合查询。"""
+38
View File
@@ -0,0 +1,38 @@
"""骨架冒烟测试:包能导入,且核心包不拉起 web 依赖(AGENTS.md §5 硬规则)。"""
from __future__ import annotations
import subprocess
import sys
def test_package_imports() -> None:
import adapter_python_pg
assert adapter_python_pg.__version__ == "0.1.0"
def test_core_import_does_not_pull_fastapi() -> None:
"""在干净的解释器里 import 核心包,sys.modules 不应出现 fastapi / uvicorn。"""
code = (
"import sys, adapter_python_pg;"
"assert 'fastapi' not in sys.modules, 'core imported fastapi';"
"assert 'uvicorn' not in sys.modules, 'core imported uvicorn';"
"print('ok')"
)
result = subprocess.run(
[sys.executable, "-c", code],
capture_output=True,
text=True,
)
assert result.returncode == 0, result.stderr
assert result.stdout.strip() == "ok"
def test_unknown_attribute_raises() -> None:
import pytest
import adapter_python_pg
with pytest.raises(AttributeError):
_ = adapter_python_pg.nonexistent_attribute
Generated
+1351
View File
File diff suppressed because it is too large. Load diff