storepy 4.2.0__tar.gz → 4.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {storepy-4.2.0/src/storepy.egg-info → storepy-4.2.1}/PKG-INFO +1 -1
- {storepy-4.2.0 → storepy-4.2.1}/pyproject.toml +1 -1
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/resource/__init__.py +4 -2
- {storepy-4.2.0 → storepy-4.2.1/src/storepy.egg-info}/PKG-INFO +1 -1
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_resource.py +41 -0
- {storepy-4.2.0 → storepy-4.2.1}/LICENSE +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/README.md +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/setup.cfg +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/__init__.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/ask.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/ask_knowledge.md +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/bootstrap.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/cache.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/core.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/crud/__init__.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/crud/exec.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/crud/id.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/crud/mutation.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/crud/query.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/crud/write.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/datasource.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/ddl.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/executors/__init__.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/executors/_values.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/executors/mongo.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/executors/mysql.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/executors/postgres.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/executors/sqlite.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/feedback.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/introspect/__init__.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/introspect/mysql.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/introspect/postgres.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/introspect/sqlite.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/llm.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/load.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/metadef.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/naming.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/permission.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/resource/providers/__init__.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/resource/providers/local.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/resource/providers/s3.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/schema.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/sync.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/py_store/workflow.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/storepy.egg-info/SOURCES.txt +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/storepy.egg-info/dependency_links.txt +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/storepy.egg-info/requires.txt +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/src/storepy.egg-info/top_level.txt +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_ask.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_cache.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_coverage_margin.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_federation_e2e.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_feedback_flush.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_fns_binding.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_host_contract.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_host_paths.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_load.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_metadef.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_migration.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_mongo_executor.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_mongo_session.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_multi_datasource.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_parity_deny.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_profile.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_py_store.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_raw_sql_and_ddl.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_rbac_host.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_real_backends_e2e.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_require_context.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_resource_e2e.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_resource_units.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_scenario_course_platform.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_scenario_manager_transaction.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_session.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_sql_commit_and_registry.py +0 -0
- {storepy-4.2.0 → storepy-4.2.1}/tests/test_workflow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: storepy
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: Multi-backend data layer for Python asyncio (MongoDB, MySQL, SQLite, PostgreSQL): pure JSON schemas, GQL tree queries compiled to a single native query, GROUP BY/HAVING aggregation, computed columns, soft-delete and role-based access control
|
|
5
5
|
Author-email: leo <coen_ddt@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "storepy"
|
|
7
|
-
version = "4.2.
|
|
7
|
+
version = "4.2.1"
|
|
8
8
|
description = "Multi-backend data layer for Python asyncio (MongoDB, MySQL, SQLite, PostgreSQL): pure JSON schemas, GQL tree queries compiled to a single native query, GROUP BY/HAVING aggregation, computed columns, soft-delete and role-based access control"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -130,8 +130,10 @@ async def open(resource_id, *, order=None):
|
|
|
130
130
|
"message": f'资源副本读取失败,降级到下一副本(backend={loc["backend"]}, id={resource_id}):{e}',
|
|
131
131
|
"hint": "检查该 provider 可用性;该副本可能需要重建"})
|
|
132
132
|
if last_err is not None:
|
|
133
|
-
raise last_err
|
|
134
|
-
|
|
133
|
+
raise last_err # 有副本行但读取失败:原样重抛(500 透传),禁改语义
|
|
134
|
+
# core 稳定前缀(与 ERR_PERMISSION: / ERR_GQL_PARSE: 同构):适配层按前缀判定 → 404,
|
|
135
|
+
# 禁按中文文案匹配。仅「按 resource_id 查到零 ResourceLocation 行」时抛出。
|
|
136
|
+
raise FileNotFoundError(f"ERR_RESOURCE_NOT_FOUND:资源不存在或无可读副本: {resource_id}")
|
|
135
137
|
|
|
136
138
|
|
|
137
139
|
async def remove(resource_id):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: storepy
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.1
|
|
4
4
|
Summary: Multi-backend data layer for Python asyncio (MongoDB, MySQL, SQLite, PostgreSQL): pure JSON schemas, GQL tree queries compiled to a single native query, GROUP BY/HAVING aggregation, computed columns, soft-delete and role-based access control
|
|
5
5
|
Author-email: leo <coen_ddt@qq.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -10,6 +10,8 @@ import asyncio
|
|
|
10
10
|
import re
|
|
11
11
|
import tempfile
|
|
12
12
|
|
|
13
|
+
import pytest
|
|
14
|
+
|
|
13
15
|
from py_store import feedback, resource
|
|
14
16
|
|
|
15
17
|
|
|
@@ -156,6 +158,45 @@ def test_open_degrade_on_provider_failure():
|
|
|
156
158
|
_run(scenario())
|
|
157
159
|
|
|
158
160
|
|
|
161
|
+
def test_open_missing_resource_raises_prefixed_error():
|
|
162
|
+
"""零副本行 → 抛 FileNotFoundError 且带 ERR_RESOURCE_NOT_FOUND: 稳定前缀(spec/03 判定顺序第 4 层)。"""
|
|
163
|
+
async def scenario():
|
|
164
|
+
store = _FakeStore()
|
|
165
|
+
resource.register_provider('memory', _MemoryProvider)
|
|
166
|
+
resource.configure({'store': store, 'providers': [{'kind': 'memory'}], 'url': {}})
|
|
167
|
+
|
|
168
|
+
rid = '0' * 40
|
|
169
|
+
prefix = 'ERR_RESOURCE_NOT_FOUND:'
|
|
170
|
+
with pytest.raises(FileNotFoundError) as ei:
|
|
171
|
+
await resource.open(rid)
|
|
172
|
+
assert str(ei.value).startswith(prefix), str(ei.value)
|
|
173
|
+
assert str(ei.value)[len(prefix):] == f'资源不存在或无可读副本: {rid}'
|
|
174
|
+
|
|
175
|
+
_run(scenario())
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_open_provider_failure_reraises_without_prefix():
|
|
179
|
+
"""有副本行但 provider 读取失败 → 原样重抛、不带前缀(保持 500 语义,禁伪装成「不存在」)。"""
|
|
180
|
+
async def scenario():
|
|
181
|
+
store = _FakeStore()
|
|
182
|
+
prev = feedback.get_sink()
|
|
183
|
+
feedback.set_sink(lambda e: None)
|
|
184
|
+
try:
|
|
185
|
+
resource.register_provider('badget', _BrokenGetProvider)
|
|
186
|
+
resource.configure({'store': store, 'providers': [{'kind': 'badget'}], 'url': {}})
|
|
187
|
+
store.rows.append({'_schema': 'ResourceLocation', 'resourceId': 'a' * 40,
|
|
188
|
+
'backend': 'badget', 'key': 'k', 'status': 'ok', 'priority': 0})
|
|
189
|
+
|
|
190
|
+
with pytest.raises(RuntimeError) as ei:
|
|
191
|
+
await resource.open('a' * 40)
|
|
192
|
+
assert str(ei.value) == 'disk boom'
|
|
193
|
+
assert not str(ei.value).startswith('ERR_RESOURCE_NOT_FOUND:')
|
|
194
|
+
finally:
|
|
195
|
+
feedback.set_sink(prev)
|
|
196
|
+
|
|
197
|
+
_run(scenario())
|
|
198
|
+
|
|
199
|
+
|
|
159
200
|
def test_partial_write_failure_persists_failed_and_feedback():
|
|
160
201
|
async def scenario():
|
|
161
202
|
store = _FakeStore()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|