Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 44 additions & 16 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,54 @@ Arrows mean "may import". Anything not drawn is forbidden.
plotting CLI) └─────┬──────┘
┌──────────────┼───────────────────────┐
▼ ▼ ▼
mslib.mscolab mslib.support mslib.utils
(events/message_ (vendored qt_json_view) (shared: config, constants,
type constants coordinates, units,
ONLY) netCDF, auth, qt helpers)
▲
mslib.mscolab.api mslib.support mslib.utils
(typed REST (vendored (shared: config, constants,
contract, socket qt_json_view) coordinates, units,
vocabulary; stdlib netCDF, auth, qt helpers)
only, ONLY this of ▲
mslib.mscolab) │
▲ │
mslib.mscolab (server) ─────────────────────┤
(never imports msui/mswms) │
mslib.mswms ────────────────────────────────┘
(WMS server; never imports msui/mscolab)

mslib.plugins → mslib.msui.flighttrack (grandfathered), mslib.utils
mslib.msidp → standalone SAML identity provider
mslib.msidp → standalone SAML identity provider: imports no other mslib
package, and no other package imports it
```

The servers' `blueprints.docs` still import `msui.icons` — a to-do listed below.

Besides the arrows, the contracts keep third-party stacks where they belong.
"Server stack" means Flask and its extensions, SQLAlchemy, Alembic, Werkzeug
and pysaml2:

- `mslib.utils` imports no Qt, no keyring and no server stack (`utils-base-layer`);
- `mslib.mscolab.api` imports no other mslib package, no Qt, no numpy and no
server stack (`api-leaf`);
- the servers `mswms`/`mscolab` never import Qt (`servers-without-qt`);
- the client (`msui`, `plugins`, `support`, `autoplot`) never imports the
server stack (`client-without-server-stack`);
- `mslib.msidp` imports no Qt, numpy or Flask (`msidp-standalone`), and no
other package imports it (`msidp-not-imported`).

This is what lets the conda recipe ship the packages separately (package
split, #2307).

Grandfathered violations (do not add new ones; the lists live in `setup.cfg`
under `ignore_imports` and must only shrink):

- `plugins.io.{csv,text,flitestar}` import `msui.flighttrack`.
- the deprecation shim `utils/mssautoplot.py` re-exports `mslib.autoplot`
(delete the shim next release and the entry with it). A twin shim exists at
`msui/constants.py` → `utils/constants.py`.
- `{mscolab,mswms}.blueprints.docs` import `msui.icons` — invisible to the
linter (`blueprints/` are namespace packages without `__init__.py`), but
equally discouraged.
- `plugins.io.{csv,text,flitestar}` import `msui.flighttrack` (all of them
ship in the `mss-msui` package).
- the deprecation shim `msui/constants.py` re-exports `utils/constants.py`
(`docs/samples/plugins/navaid.py` uses it).
- To-dos of the package split (#2307), each labelled with the change that
removes it: `{mscolab,mswms}.blueprints.docs`
import `msui.icons`; `utils` modules import Qt, `mslib.support`, Flask and
keyring (`utils-base-layer`, `client-without-server-stack`). import-linter
reports an entry whose import is gone ("No matches for ignored import"), so
the PR that removes an import has to delete its entry.

## Module index

Expand Down Expand Up @@ -118,10 +144,12 @@ under `ignore_imports` and must only shrink):
and `tests/_test_mscolab/test_api_contract.py` (schemas against real routes).
3. All configuration access goes through `mslib.utils.config.config_loader`;
never read the settings JSON directly.
4. Qt imports are allowed only in `mslib/msui`, `mslib/utils/{qt,colordialog}.py`,
and `mslib/support`.
4. Qt imports are allowed only in `mslib/msui` and `mslib/support` — and, until
they move to msui, in the `mslib/utils` modules listed in the
`utils-base-layer` contract (`qt`, `colordialog`, `config`, `airdata`,
`migration/`).
5. New cross-package imports must satisfy the import-linter contracts in
`setup.cfg` — never extend an `ignore_imports` list.
`setup.cfg` — never extend an `ignore_imports` list; its entries only shrink.

## Verification ladder (cheapest first)

Expand Down
5 changes: 3 additions & 2 deletions mslib/mscolab/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ Global map: ../../ARCHITECTURE.md
## May import

`mslib.utils` only (plus stdlib/Flask stack). Never `mslib.msui` (exception:
`blueprints/docs` icons, grandfathered) or `mslib.mswms`. Enforced by the
`gui-isolation` contract in setup.cfg.
`blueprints/docs` icons, a to-do of the package split, #2307) or
`mslib.mswms`, and never Qt. Enforced by the `gui-isolation`, `wms-isolation`
and `servers-without-qt` contracts in setup.cfg.

## Invariants

Expand Down
25 changes: 25 additions & 0 deletions mslib/mscolab/blueprints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""

mslib.mscolab.blueprints
~~~~~~~~~~~~~~~~~~~~~~~~

Blueprints of the mscolab server

This file is part of MSS.

:copyright: Copyright 2026 Reimar Bauer
:license: APACHE-2.0, see LICENSE for details.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
8 changes: 4 additions & 4 deletions mslib/msui/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ Global map: ../../ARCHITECTURE.md

## May import

`mslib.utils`, `mslib.support`, and ONLY `mslib.mscolab.{events,message_type,
api.schemas,api.endpoints}` from the server package (enforced:
`server-isolation` contract in setup.cfg). Never `mslib.mswms` — WMS is
consumed over HTTP.
`mslib.utils`, `mslib.support`, and ONLY `mslib.mscolab.api.*` from the server
package (enforced: `server-isolation` contract in setup.cfg). Never
`mslib.mswms` — WMS is consumed over HTTP — and never the server stack (Flask,
SQLAlchemy; `client-without-server-stack` contract).

## Invariants

Expand Down
5 changes: 3 additions & 2 deletions mslib/mswms/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ Global map: ../../ARCHITECTURE.md

## May import

`mslib.utils` only. Never `mslib.msui` (exception: `blueprints/docs` icons,
grandfathered) or `mslib.mscolab`. Enforced: `wms-isolation` contract.
`mslib.utils` only. Never `mslib.msui` (exception: `blueprints/docs` icons, a
to-do of the package split, #2307) or `mslib.mscolab`, and never Qt.
Enforced: `gui-isolation`, `server-isolation`, `servers-without-qt` contracts.

## Invariants

Expand Down
25 changes: 25 additions & 0 deletions mslib/mswms/blueprints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
"""

mslib.mswms.blueprints
~~~~~~~~~~~~~~~~~~~~~~

Blueprints of the mswms server

This file is part of MSS.

:copyright: Copyright 2026 Reimar Bauer
:license: APACHE-2.0, see LICENSE for details.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
18 changes: 14 additions & 4 deletions mslib/utils/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,26 @@ Global map: ../../ARCHITECTURE.md

## May import

Nothing from `mslib` outside `utils` — this is the base layer, enforced by
the `gui-isolation` contract in setup.cfg.
Nothing from `mslib` outside `utils`, and no Qt, keyring or server stack
(Flask & co., SQLAlchemy, Werkzeug, pysaml2) — this is the base layer, enforced
by the `gui-isolation` and `utils-base-layer` contracts in setup.cfg. The
imports that still break this are listed there as to-dos of the package
split (#2307); don't add more:

- Qt: `qt`, `colordialog`, `config`, `airdata` and `migration/` import PyQt5
(`config` and `migration/` also `mslib.support`), so `find_location` and
`ogcwms`, which import `config`, need Qt as well;
- keyring: `auth`, `migration/update_json_file_to_version_eight`;
- Flask: `auth` (`send_email`).

## Invariants

- New config keys: add the attribute on `MSUIDefaultConfig` AND, if dict/list
shaped, the matching entry in `dict_option_structure`/`list_option_structure`
and a line in `config_descriptions`.
- Everything except `qt.py`/`colordialog.py` (Qt) must stay importable without Qt
or a running server.
- Everything except the modules above that need Qt (`qt`, `colordialog`,
`config`, `airdata`, `migration/`, `find_location`, `ogcwms`) must stay
importable without Qt or a running server.

## Verify

Expand Down
132 changes: 131 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ ignore_imports =
mslib.plugins.io.csv -> mslib.msui.flighttrack
mslib.plugins.io.text -> mslib.msui.flighttrack
mslib.plugins.io.flitestar -> mslib.msui.flighttrack
# To do (package split, #2307): the docs blueprints get their own favicon/logo.
mslib.mswms.blueprints.docs -> mslib.msui.icons
mslib.mscolab.blueprints.docs -> mslib.msui.icons

[importlinter:contract:server-isolation]
name = Only the shared protocol constants of mslib.mscolab may be imported elsewhere
Expand All @@ -91,10 +94,12 @@ ignore_imports =
mslib.msui.* -> mslib.mscolab.api.*

[importlinter:contract:api-leaf]
name = mslib.mscolab.api must not pull in the server stack
name = mslib.mscolab.api (the mss-mscolab-api package) needs nothing but the standard library
type = forbidden
source_modules =
mslib.mscolab.api
# the rest of mslib.mscolab is listed module by module: a new module of mslib.mscolab
# has to be added here
forbidden_modules =
mslib.mscolab.app
mslib.mscolab.auth
Expand All @@ -114,10 +119,25 @@ forbidden_modules =
mslib.mscolab.utils
mslib.msui
mslib.mswms
mslib.utils
mslib.support
mslib.plugins
mslib.autoplot
mslib.msidp
PyQt5
numpy
flask
flask_cors
flask_httpauth
flask_mail
flask_migrate
flask_socketio
flask_sqlalchemy
flask_wtf
alembic
sqlalchemy
werkzeug
saml2

[importlinter:contract:wms-isolation]
name = No other mslib package may import the mswms server
Expand All @@ -132,3 +152,113 @@ source_modules =
mslib.autoplot
forbidden_modules =
mslib.mswms

[importlinter:contract:utils-base-layer]
name = mslib.utils is the base layer: no other mslib package, no Qt, no keyring, no server stack
type = forbidden
source_modules =
mslib.utils
forbidden_modules =
mslib.support
mslib.autoplot
mslib.plugins
mslib.msidp
PyQt5
keyring
flask
flask_cors
flask_httpauth
flask_mail
flask_migrate
flask_socketio
flask_sqlalchemy
flask_wtf
alembic
sqlalchemy
werkzeug
saml2
# Every line below is a to-do of the package split (#2307) and goes with the change that removes the import;
# import-linter reports a line whose import is gone, so the list can only shrink.
ignore_imports =
# to do: send_email moves to mslib.mscolab
mslib.utils.auth -> flask
mslib.utils.auth -> flask_mail
# to do: the Qt client modules move to mslib.msui
mslib.utils.airdata -> PyQt5
mslib.utils.colordialog -> PyQt5
mslib.utils.config -> PyQt5
mslib.utils.config -> mslib.support.qt_json_view.datatypes
mslib.utils.migration.config_before_eight -> PyQt5
mslib.utils.migration.config_before_eight -> mslib.support.qt_json_view.datatypes
mslib.utils.migration.config_before_nine -> PyQt5
mslib.utils.migration.config_before_nine -> mslib.support.qt_json_view.datatypes
mslib.utils.migration.update_json_file_to_version_eight -> keyring
mslib.utils.qt -> PyQt5
# to do: the client-only helpers move to mslib.msui
mslib.utils.auth -> keyring

[importlinter:contract:msidp-standalone]
name = mslib.msidp is standalone: no other mslib package, no Qt, numpy or flask
type = forbidden
source_modules =
mslib.msidp
forbidden_modules =
mslib.utils
mslib.msui
mslib.mswms
mslib.mscolab
mslib.plugins
mslib.support
mslib.autoplot
PyQt5
numpy
flask

[importlinter:contract:msidp-not-imported]
name = No other mslib package imports mslib.msidp (mss-msidp is a package of its own)
type = forbidden
source_modules =
mslib.utils
mslib.msui
mslib.mswms
mslib.mscolab
mslib.plugins
mslib.support
mslib.autoplot
forbidden_modules =
mslib.msidp

[importlinter:contract:servers-without-qt]
name = The servers mswms and mscolab never import Qt
type = forbidden
source_modules =
mslib.mswms
mslib.mscolab
forbidden_modules =
PyQt5

[importlinter:contract:client-without-server-stack]
name = The client (msui, plugins, support, autoplot) never imports the server stack
type = forbidden
source_modules =
mslib.msui
mslib.plugins
mslib.support
mslib.autoplot
forbidden_modules =
flask
flask_cors
flask_httpauth
flask_mail
flask_migrate
flask_socketio
flask_sqlalchemy
flask_wtf
alembic
sqlalchemy
werkzeug
saml2
ignore_imports =
# to do (package split, #2307): send_email moves to mslib.mscolab
mslib.utils.auth -> flask
mslib.utils.auth -> flask_mail
Loading