python-ddd-framework 0.7.0__py3-none-any.whl → 0.7.1__py3-none-any.whl
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.
- python_ddd_framework/fastapi/adapter.py +25 -10
- python_ddd_framework/fastapi/health.py +20 -11
- python_ddd_framework/fastapi/routing.py +1 -1
- python_ddd_framework/sqlalchemy/migration.py +17 -1
- {python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/METADATA +10 -2
- {python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/RECORD +10 -10
- {python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/WHEEL +1 -1
- {python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/entry_points.txt +0 -0
- {python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/licenses/LICENSE +0 -0
- {python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt +0 -0
|
@@ -15,7 +15,13 @@ from starlette.middleware import Middleware
|
|
|
15
15
|
from ..application import Application
|
|
16
16
|
from ..application_services import ApplicationServiceValidationError
|
|
17
17
|
from ..authorization import PermissionDeniedError, UnauthenticatedError
|
|
18
|
-
from ..errors import
|
|
18
|
+
from ..errors import (
|
|
19
|
+
BusinessError,
|
|
20
|
+
FrameworkError,
|
|
21
|
+
InvalidApplicationStateError,
|
|
22
|
+
ResourceConflictError,
|
|
23
|
+
ResourceNotFoundError,
|
|
24
|
+
)
|
|
19
25
|
from ..lifecycle import ApplicationState
|
|
20
26
|
from .contracts import RouterCatalog
|
|
21
27
|
from .error_handlers import (
|
|
@@ -30,6 +36,7 @@ from .error_handlers import (
|
|
|
30
36
|
)
|
|
31
37
|
from .errors import FastApiAdapterError
|
|
32
38
|
from .filters import ServiceFilter, _validate_filters
|
|
39
|
+
from .health import _APPLICATION_STATE_ATTRIBUTE
|
|
33
40
|
from .middleware import DiMiddleware, _compile_middlewares
|
|
34
41
|
from .module import FastApiModule
|
|
35
42
|
from .realtime.runtime import _FastApiRealtimeRuntime
|
|
@@ -174,6 +181,7 @@ class FastApiAdapter:
|
|
|
174
181
|
app.state.python_ddd_framework_filters = self._filters
|
|
175
182
|
app.state.python_ddd_framework_upload_limits = self._upload_limits
|
|
176
183
|
app.state.dishka_container = self._application._container_for_integration()
|
|
184
|
+
setattr(app.state, _APPLICATION_STATE_ATTRIBUTE, self._application)
|
|
177
185
|
app.user_middleware = [*middlewares, Middleware(_RequestTaskMiddleware)]
|
|
178
186
|
app.add_middleware(_RequestContextMiddleware, application=self._application)
|
|
179
187
|
_install_tracing(self._application, app)
|
|
@@ -191,17 +199,19 @@ class FastApiAdapter:
|
|
|
191
199
|
try:
|
|
192
200
|
self._validate_start_target(app)
|
|
193
201
|
except BaseException:
|
|
194
|
-
if self._installed_app is app:
|
|
202
|
+
if self._installed_app is app and self._application.state is ApplicationState.COMPOSED:
|
|
195
203
|
try:
|
|
196
|
-
|
|
197
|
-
await self._application.stop()
|
|
204
|
+
await self._application.stop()
|
|
198
205
|
finally:
|
|
199
|
-
self.
|
|
206
|
+
self._clear_application_bridges(app)
|
|
200
207
|
raise
|
|
201
208
|
try:
|
|
202
209
|
await self._application.start()
|
|
210
|
+
except InvalidApplicationStateError:
|
|
211
|
+
# Core 在取得转换 ownership 前拒绝重复/并发进入;不能撤销有效 lifespan 的桥。
|
|
212
|
+
raise
|
|
203
213
|
except BaseException:
|
|
204
|
-
self.
|
|
214
|
+
self._clear_application_bridges(app)
|
|
205
215
|
raise
|
|
206
216
|
|
|
207
217
|
try:
|
|
@@ -214,7 +224,7 @@ class FastApiAdapter:
|
|
|
214
224
|
await self._application.stop()
|
|
215
225
|
finally:
|
|
216
226
|
# Container 的 close 始终属于 Application;这里只撤销 Web 引用。
|
|
217
|
-
self.
|
|
227
|
+
self._clear_application_bridges(app)
|
|
218
228
|
|
|
219
229
|
def _validate_install_target(self, app: FastAPI) -> None:
|
|
220
230
|
if not isinstance(app, FastAPI):
|
|
@@ -225,6 +235,8 @@ class FastApiAdapter:
|
|
|
225
235
|
raise FastApiAdapterError("FastAPI instance already has a Python DDD Framework adapter")
|
|
226
236
|
if hasattr(app.state, _DISHKA_CONTAINER_BRIDGE):
|
|
227
237
|
raise FastApiAdapterError("FastAPI instance already has a Dishka container bridge")
|
|
238
|
+
if hasattr(app.state, _APPLICATION_STATE_ATTRIBUTE):
|
|
239
|
+
raise FastApiAdapterError("FastAPI instance already has an Application binding")
|
|
228
240
|
if app.middleware_stack is not None:
|
|
229
241
|
raise FastApiAdapterError("FastAPI middleware stack was already built before install")
|
|
230
242
|
if self._application.state is not ApplicationState.COMPOSED:
|
|
@@ -239,6 +251,8 @@ class FastApiAdapter:
|
|
|
239
251
|
)
|
|
240
252
|
if getattr(app.state, _INSTALLATION_MARKER, None) != str(self._application.id):
|
|
241
253
|
raise FastApiAdapterError("FastAPI installation marker changed before startup")
|
|
254
|
+
if getattr(app.state, _APPLICATION_STATE_ATTRIBUTE, None) is not self._application:
|
|
255
|
+
raise FastApiAdapterError("FastAPI Application binding changed before startup")
|
|
242
256
|
if app.state.python_ddd_framework_filters != self._filters:
|
|
243
257
|
raise FastApiAdapterError("HTTP filters changed after adapter installation")
|
|
244
258
|
if tuple(app.user_middleware) != self._installed_middleware:
|
|
@@ -258,9 +272,10 @@ class FastApiAdapter:
|
|
|
258
272
|
raise FastApiAdapterError("Dishka container bridge changed before startup")
|
|
259
273
|
|
|
260
274
|
@staticmethod
|
|
261
|
-
def
|
|
262
|
-
|
|
263
|
-
|
|
275
|
+
def _clear_application_bridges(app: FastAPI) -> None:
|
|
276
|
+
for attribute in (_DISHKA_CONTAINER_BRIDGE, _APPLICATION_STATE_ATTRIBUTE):
|
|
277
|
+
if hasattr(app.state, attribute):
|
|
278
|
+
delattr(app.state, attribute)
|
|
264
279
|
|
|
265
280
|
async def _shutdown_realtime(self) -> None:
|
|
266
281
|
try:
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from fastapi import APIRouter
|
|
5
|
+
from fastapi import APIRouter, Request
|
|
6
6
|
from starlette.responses import JSONResponse
|
|
7
7
|
|
|
8
8
|
from ..application import Application
|
|
9
|
+
from .errors import FastApiAdapterError
|
|
10
|
+
|
|
11
|
+
_APPLICATION_STATE_ATTRIBUTE = "_python_ddd_framework_application"
|
|
9
12
|
|
|
10
13
|
HEALTH_LIVE_PATH = "/health/live"
|
|
11
14
|
HEALTH_READY_PATH = "/health/ready"
|
|
@@ -13,18 +16,24 @@ HEALTH_LIVE_OPERATION_ID = "python_ddd_framework_health_live"
|
|
|
13
16
|
HEALTH_READY_OPERATION_ID = "python_ddd_framework_health_ready"
|
|
14
17
|
|
|
15
18
|
|
|
16
|
-
def
|
|
17
|
-
|
|
19
|
+
async def live() -> JSONResponse:
|
|
20
|
+
return JSONResponse(status_code=200, content={"status": "live"})
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def ready(request: Request) -> JSONResponse:
|
|
24
|
+
# FastAPI 可长期缓存 callable;应用引用只在当前请求期间从实例状态取得。
|
|
25
|
+
application = getattr(request.app.state, _APPLICATION_STATE_ATTRIBUTE, None)
|
|
26
|
+
if not isinstance(application, Application):
|
|
27
|
+
raise FastApiAdapterError("FastAPI Application binding is unavailable")
|
|
28
|
+
is_ready = application.is_ready
|
|
29
|
+
return JSONResponse(
|
|
30
|
+
status_code=200 if is_ready else 503,
|
|
31
|
+
content={"status": "ready" if is_ready else "not_ready"},
|
|
32
|
+
)
|
|
18
33
|
|
|
19
|
-
async def live() -> JSONResponse:
|
|
20
|
-
return JSONResponse(status_code=200, content={"status": "live"})
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return JSONResponse(
|
|
25
|
-
status_code=200 if is_ready else 503,
|
|
26
|
-
content={"status": "ready" if is_ready else "not_ready"},
|
|
27
|
-
)
|
|
35
|
+
def _create_health_router() -> APIRouter:
|
|
36
|
+
router = APIRouter(tags=["health"])
|
|
28
37
|
|
|
29
38
|
router.add_api_route(
|
|
30
39
|
HEALTH_LIVE_PATH,
|
|
@@ -135,7 +135,7 @@ def _build_route_plan(
|
|
|
135
135
|
)
|
|
136
136
|
)
|
|
137
137
|
|
|
138
|
-
health_router = _create_health_router(
|
|
138
|
+
health_router = _create_health_router()
|
|
139
139
|
health_route_start = len(preview.routes)
|
|
140
140
|
preview.include_router(health_router)
|
|
141
141
|
health_candidates = _route_candidates(
|
|
@@ -332,6 +332,11 @@ def _validate_revision_graph(
|
|
|
332
332
|
# walk_revisions 强制 Alembic 在任何 engine 创建前加载并校验组合 graph/depends_on。
|
|
333
333
|
script = ScriptDirectory.from_config(config)
|
|
334
334
|
revisions = tuple(script.walk_revisions())
|
|
335
|
+
empty_model_sources = {
|
|
336
|
+
(item.owner, item.registration.branch_label, item.migration_path)
|
|
337
|
+
for item in models.for_connection(connection_name)
|
|
338
|
+
if not item.registration.metadata.tables
|
|
339
|
+
}
|
|
335
340
|
for source in migration_sources:
|
|
336
341
|
if source.extension is not None:
|
|
337
342
|
_validate_extension_prerequisites(models, connection_name, source, script)
|
|
@@ -341,7 +346,18 @@ def _validate_revision_graph(
|
|
|
341
346
|
if Path(revision.path).resolve().parent == source.migration_path
|
|
342
347
|
)
|
|
343
348
|
if not owned_revisions:
|
|
344
|
-
|
|
349
|
+
# 仅全局操作豁免纯 ORM 骨架的缺失 revision;不跳过图或数据库 heads 检查。
|
|
350
|
+
# 同一 owner 可贡献多个来源,判定必须限定当前 connection 与具体分支/目录。
|
|
351
|
+
if (
|
|
352
|
+
(owner is not None and source.owner != owner)
|
|
353
|
+
or allow_empty
|
|
354
|
+
or (
|
|
355
|
+
owner is None
|
|
356
|
+
and source.extension is None
|
|
357
|
+
and (source.owner, source.branch_label, source.migration_path)
|
|
358
|
+
in empty_model_sources
|
|
359
|
+
)
|
|
360
|
+
):
|
|
345
361
|
continue
|
|
346
362
|
raise SqlAlchemyModelRegistrationError(
|
|
347
363
|
owner=str(source.owner),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-ddd-framework
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Modular DDD application framework with project CLI and bundled templates
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
@@ -304,6 +304,14 @@ The image uses `python -m host.main` without development reload. Runtime install
|
|
|
304
304
|
|
|
305
305
|
New `ddd` modules contain the full responsibility directories and necessary composition, with no preset business code or tests. `basic` is unchanged. This affects subsequent generation only: upgrades do not delete or rewrite existing consumers' order code. Keep existing business contracts and data under their application owners. The former order implementation and its existing domain test now live in [the independent example](examples/order_management/README.md); its initializer uses the official CLI, requires a new destination, and is not bundled in the runtime wheel. No CLI parameter or dependency has been added.
|
|
306
306
|
|
|
307
|
+
## HTTP lifecycle and global migration checks (0.7.1)
|
|
308
|
+
|
|
309
|
+
- Health endpoints now read their Application from request-local access to the FastAPI instance state. Adapter rollback and lifespan cleanup release the binding; rejected duplicate or concurrent lifespan entry preserves the active lifespan's binding and readiness. Health URLs, responses and readiness semantics are unchanged.
|
|
310
|
+
- Replace Owner filtering used solely to exclude empty ORM skeletons with one `await SqlAlchemyMigrator(application).check_current_heads()` call. Global checks and upgrades waive missing revisions only for ordinary ORM sources whose metadata and revision history are both empty. Sources with tables, historical revisions, migration-only declarations or extensions retain their checks, including unknown database history.
|
|
311
|
+
- Keep the Host's startup orchestration and cleanup. Run the check while the Application is COMPOSED, before starting it; there is no automatic startup check or new configuration. Explicit module operations and first-revision authoring keep their existing behavior.
|
|
312
|
+
|
|
313
|
+
See the [migration guide](docs/development.md#数据库聚合与迁移) and [verification record](docs/status.md#http-回收与迁移来源判定未发布).
|
|
314
|
+
|
|
307
315
|
<a id="streaming-and-access-migration-07-unreleased"></a>
|
|
308
316
|
|
|
309
317
|
## Streaming and access migration (0.7)
|
|
@@ -546,7 +554,7 @@ Do not remove durable job payload versions merely because their Python APIs were
|
|
|
546
554
|
<details>
|
|
547
555
|
<summary>For maintainers: publishing a changed version</summary>
|
|
548
556
|
|
|
549
|
-
Routine code and documentation commits may retain the current version.
|
|
557
|
+
Routine code and documentation commits may retain the current version. Pushes to `main` run the full release gates, including locked, lowest-direct, and highest allowed dependency samples, but skip publication unless the version increased across the push; initial creation of `main` publishes its current version. Manual workflow runs validate only. A failed run may be retried, but a later same-version push does not resume publication: changed contents require another version bump. Follow [release operations](https://github.com/componet-architecture/python-ddd-framework/blob/main/docs/development.md#发布操作) for publisher setup and retry boundaries. Dependency samples and current API/type checks do not establish backward compatibility or a cross-version upgrade guarantee.
|
|
550
558
|
|
|
551
559
|
</details>
|
|
552
560
|
|
|
@@ -220,14 +220,14 @@ python_ddd_framework/fastapi/__init__.py,sha256=H_c5k2XhbaNdjRXXxfnd1ibwApaDtSh5
|
|
|
220
220
|
python_ddd_framework/fastapi/access.py,sha256=SWMB_ILM8iItiS0gftX1rNmKy9gO2BCJDaEiWa54ScI,5346
|
|
221
221
|
python_ddd_framework/fastapi/access_options.py,sha256=iXP7X4JCZ-7TcifgOdFB65RhZdZNVKF7l21AUKD-3ws,383
|
|
222
222
|
python_ddd_framework/fastapi/action.py,sha256=OnOrnlgS2xEkpMqVX0s_y2uS4MaGR7QdKfS9bJWlx1U,7459
|
|
223
|
-
python_ddd_framework/fastapi/adapter.py,sha256=
|
|
223
|
+
python_ddd_framework/fastapi/adapter.py,sha256=SgnCqcVmq_DwK2UiI2PmZPA5Bygxi1QBtp31ym7SKAg,14120
|
|
224
224
|
python_ddd_framework/fastapi/application_services.py,sha256=RixZNC5niVAWzShUqfxFU_THHQlzxTbwfUTdxETU3qM,11909
|
|
225
225
|
python_ddd_framework/fastapi/background.py,sha256=MZASgFRz1tQkzMo7A33ZA41qS90wpboLrOdL3RHc87w,1616
|
|
226
226
|
python_ddd_framework/fastapi/contracts.py,sha256=I9EFDcAuv_Xlv1iNxKWIo4krt3-vkfJgV4ngPKWhxPM,11888
|
|
227
227
|
python_ddd_framework/fastapi/error_handlers.py,sha256=jH8tbW9nGd_znwhZHSDZv3KwN7k59FFi0QZQWpCWLcg,4492
|
|
228
228
|
python_ddd_framework/fastapi/errors.py,sha256=XPhxj-WyWQ2wj1q1JkalnyVi2UaOKHbFk_-RbMnS5Ok,984
|
|
229
229
|
python_ddd_framework/fastapi/filters.py,sha256=aQQB4imJa9XaZUsVkIwmkZ9iZH54P_36wulc7RIKuXU,3954
|
|
230
|
-
python_ddd_framework/fastapi/health.py,sha256=
|
|
230
|
+
python_ddd_framework/fastapi/health.py,sha256=Gjoebhx-6fH4go48xmw3dfo9qUKhVHBxcZfbxkB5Ndg,1703
|
|
231
231
|
python_ddd_framework/fastapi/http.py,sha256=W0jFoqVYOc_rvT7BGa9m1btEThixX7o0ujt_TMxegsg,2725
|
|
232
232
|
python_ddd_framework/fastapi/http_router.py,sha256=pgfzy7zcUgy8bNOUTIRn7vgeSHLLcVcVbufLute_m_Y,9254
|
|
233
233
|
python_ddd_framework/fastapi/manual_action.py,sha256=1HMqRttXK1JiN3uuKGmYuss9hRAzussI-dilGhGfns8,5992
|
|
@@ -242,7 +242,7 @@ python_ddd_framework/fastapi/realtime/options.py,sha256=wfEPu6q6dmydx5m6h9E_muie
|
|
|
242
242
|
python_ddd_framework/fastapi/realtime/runtime.py,sha256=OL0tAOcLDUY_IXlkzXMLzPkAynpa1jExbtliDbmtct8,15197
|
|
243
243
|
python_ddd_framework/fastapi/request_context.py,sha256=3EWO44HgumKQOFHeyP_dVF3qTRbo1lzbhR1K1qSvxqo,10592
|
|
244
244
|
python_ddd_framework/fastapi/route_integrity.py,sha256=_tDeeE6XHbsgrA2i1m6Yz_I9NU_JVuYb1E3iMvbwMxE,3174
|
|
245
|
-
python_ddd_framework/fastapi/routing.py,sha256=
|
|
245
|
+
python_ddd_framework/fastapi/routing.py,sha256=dvTBWAJVV8TINtY98vCCsN_NVCZGi5h75XqoDfzfRMQ,16342
|
|
246
246
|
python_ddd_framework/fastapi/server.py,sha256=o8klk9sUJvBuLRBM8VU5bp1pFJwRHYafrImI5VGK8GQ,7538
|
|
247
247
|
python_ddd_framework/fastapi/service_endpoints.py,sha256=RpbC-9sEu2JF2JiwsogKnp7bpOhgvgjw6EtAadLzHuM,7549
|
|
248
248
|
python_ddd_framework/fastapi/service_routes.py,sha256=qOWYcIkOtIdYcMXdDIHan0DtklkRieqtBWXd9BQPuBU,9329
|
|
@@ -430,7 +430,7 @@ python_ddd_framework/sqlalchemy/metadata.py,sha256=cuQV6ER_PY-LdmKmpK-riQRY1mpyp
|
|
|
430
430
|
python_ddd_framework/sqlalchemy/metadata_builder.py,sha256=FaMs0jPqO4uWuK_ZjIF25xIycy6SK2BfFCpx_UXii6Y,9588
|
|
431
431
|
python_ddd_framework/sqlalchemy/metadata_fingerprint.py,sha256=LDP0Q9I_RmtJYxa84Pvhw8GLLhlxRwy010qe6FC0MYw,4307
|
|
432
432
|
python_ddd_framework/sqlalchemy/metadata_ownership.py,sha256=OO2Y-1wbn8qwz35qXdW7nV9HJqf7dc82qFHjCwr8K7I,2750
|
|
433
|
-
python_ddd_framework/sqlalchemy/migration.py,sha256=
|
|
433
|
+
python_ddd_framework/sqlalchemy/migration.py,sha256=H8ke8F-4AnjTvRdZfaZD2WJYMhUYDO3kNddtad3cy4k,18305
|
|
434
434
|
python_ddd_framework/sqlalchemy/migration_operations.py,sha256=y_hH8yc11k8wftVMhTHGGpR6-DuIPftxDeUa_YCEn0U,3770
|
|
435
435
|
python_ddd_framework/sqlalchemy/migration_options.py,sha256=4_VK8Ei1U0twC3dnpfnzUblM9x2kqhuRPgEoB9ahtQc,2082
|
|
436
436
|
python_ddd_framework/sqlalchemy/migration_sources.py,sha256=mre0ktTV9ooDXjNf_LI9zNuh1eJWatpwaWIRGd60wsA,1175
|
|
@@ -452,9 +452,9 @@ python_ddd_framework/unit_of_work/module.py,sha256=EjsUtCG7w56NmLU_xqBRiwJ65Mw5h
|
|
|
452
452
|
python_ddd_framework/unit_of_work/options.py,sha256=AVcEmwwn39qvj8PLQetBeGzdU_eblABGuNLDIQU5tyI,3365
|
|
453
453
|
python_ddd_framework/validation/__init__.py,sha256=nTN_BgIXMLHeUtMgSj2hpisWc1_sk6UWTapk30oh1us,100
|
|
454
454
|
python_ddd_framework/validation/models.py,sha256=Ydz5ZYgqYMPZEXi9A3DA6beN5lD9AcMaImnxDf1aRXs,1737
|
|
455
|
-
python_ddd_framework-0.7.
|
|
456
|
-
python_ddd_framework-0.7.
|
|
457
|
-
python_ddd_framework-0.7.
|
|
458
|
-
python_ddd_framework-0.7.
|
|
459
|
-
python_ddd_framework-0.7.
|
|
460
|
-
python_ddd_framework-0.7.
|
|
455
|
+
python_ddd_framework-0.7.1.dist-info/licenses/LICENSE,sha256=2sSWLZdtk4EhG2GlwpksXzmyux-G6Qiov0D27lE_bqw,805
|
|
456
|
+
python_ddd_framework-0.7.1.dist-info/licenses/src/python_ddd_framework/background_jobs/pgqueuer/UPSTREAM_LICENSE.txt,sha256=0lCGUd5Pke2Up1Kqn1kRnHcwLlBz60E8xvR7WL4k_kE,1066
|
|
457
|
+
python_ddd_framework-0.7.1.dist-info/WHEEL,sha256=xTCBFIZIIyRSxyowOv5Wqg_jFU6KsdIffuulBB09Ph8,80
|
|
458
|
+
python_ddd_framework-0.7.1.dist-info/entry_points.txt,sha256=CMy4XBD8IKGUfHwe0YBmHmSJtjfnA3yyDHqsMZCLsc8,447
|
|
459
|
+
python_ddd_framework-0.7.1.dist-info/METADATA,sha256=wuV2ZKpyQAD-DUgVtmUbUsGa-fTUYranSs28gys5IgI,65390
|
|
460
|
+
python_ddd_framework-0.7.1.dist-info/RECORD,,
|
{python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{python_ddd_framework-0.7.0.dist-info → python_ddd_framework-0.7.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|