goodmap 2.0.0a1__tar.gz → 2.0.0a2__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.
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/PKG-INFO +2 -2
- goodmap-2.0.0a2/goodmap/__init__.py +3 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/goodmap.py +76 -19
- goodmap-2.0.0a2/goodmap/plugin.py +75 -0
- goodmap-2.0.0a2/goodmap/static/frontend/index.min.js +246 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/pyproject.toml +2 -2
- goodmap-2.0.0a1/goodmap/__init__.py +0 -3
- goodmap-2.0.0a1/goodmap/plugin.py +0 -12
- goodmap-2.0.0a1/goodmap/static/frontend/index.min.js +0 -246
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/LICENSE.md +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/README.md +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/admin_api.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/api_models.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/clustering.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/config.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/core.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/core_api.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/data_models/location.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/db.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/exceptions.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/feature_flags.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/formatter.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/json_security.py +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/templates/goodmap-admin.html +0 -0
- {goodmap-2.0.0a1 → goodmap-2.0.0a2}/goodmap/templates/map.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: goodmap
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.0a2
|
|
4
4
|
Summary: Map engine to serve all the people :)
|
|
5
5
|
Author: Krzysztof Kolodzinski
|
|
6
6
|
Author-email: krzysztof.kolodzinski@problematy.pl
|
|
@@ -23,7 +23,7 @@ Requires-Dist: gunicorn (>=20.1,<26.0)
|
|
|
23
23
|
Requires-Dist: humanize (>=4.6.0,<5.0.0)
|
|
24
24
|
Requires-Dist: myst-parser (>=4.0.0,<5.0.0) ; extra == "docs"
|
|
25
25
|
Requires-Dist: numpy (>=2.2.0,<3.0.0)
|
|
26
|
-
Requires-Dist: platzky (==2.0.
|
|
26
|
+
Requires-Dist: platzky (==2.0.0a10)
|
|
27
27
|
Requires-Dist: pydantic (>=2.12.0,<3.0.0)
|
|
28
28
|
Requires-Dist: pysupercluster-problematy (>=0.7.8,<0.8.0)
|
|
29
29
|
Requires-Dist: scipy (>=1.15.1,<2.0.0)
|
|
@@ -25,32 +25,55 @@ from goodmap.db import (
|
|
|
25
25
|
get_location_obligatory_fields,
|
|
26
26
|
)
|
|
27
27
|
from goodmap.feature_flags import EnableAdminPanel, UseLazyLoading
|
|
28
|
+
from goodmap.plugin import CAPABILITY_BASES, GoodmapPluginBase
|
|
28
29
|
|
|
29
30
|
logger = logging.getLogger(__name__)
|
|
30
31
|
|
|
31
32
|
_PLUGIN_ENTRY_POINT_GROUP = "goodmap.plugins"
|
|
32
33
|
|
|
33
34
|
|
|
35
|
+
def _frontend_capability_bases(plugin_class: Any) -> list[type[GoodmapPluginBase]]:
|
|
36
|
+
"""The goodmap frontend capability bases a plugin subclasses (may be several).
|
|
37
|
+
|
|
38
|
+
Derived by class-based recognition, the same way platzky matches plugins against its
|
|
39
|
+
capability bases. A plugin can provide multiple frontend capabilities (e.g. an overlay
|
|
40
|
+
and a marker field) by subclassing more than one base.
|
|
41
|
+
"""
|
|
42
|
+
if not isinstance(plugin_class, type):
|
|
43
|
+
return []
|
|
44
|
+
return [base for base in CAPABILITY_BASES if issubclass(plugin_class, base)]
|
|
45
|
+
|
|
46
|
+
|
|
34
47
|
def _register_plugin_static_resources(
|
|
35
48
|
ep: importlib.metadata.EntryPoint,
|
|
36
|
-
) -> tuple[Blueprint | None, dict[str, Any]
|
|
37
|
-
"""Load a plugin's static resources and return its blueprint and manifest
|
|
49
|
+
) -> tuple[Blueprint | None, list[dict[str, Any]]]:
|
|
50
|
+
"""Load a plugin's static resources and return its blueprint and manifest entries.
|
|
38
51
|
|
|
39
|
-
Loads the plugin module, checks for a 'static' directory, and if found
|
|
40
|
-
|
|
52
|
+
Loads the plugin module, checks for a 'static' directory, and if found creates a Flask
|
|
53
|
+
blueprint plus one manifest entry per frontend capability the plugin provides. Each
|
|
54
|
+
capability points at its own Module Federation ``module``, all served from the plugin's
|
|
55
|
+
single ``remoteEntry.js``.
|
|
41
56
|
|
|
42
57
|
Args:
|
|
43
58
|
ep: The entry point for the plugin.
|
|
44
59
|
|
|
45
60
|
Returns:
|
|
46
|
-
A tuple of (blueprint,
|
|
47
|
-
has no static directory or loading fails.
|
|
61
|
+
A tuple of (blueprint, manifest_entries). The blueprint is None and the list empty
|
|
62
|
+
if the plugin has no static directory, no frontend capability, or loading fails.
|
|
48
63
|
"""
|
|
49
64
|
try:
|
|
50
|
-
|
|
65
|
+
plugin_class = ep.load()
|
|
66
|
+
mod_path = os.path.dirname(os.path.realpath(inspect.getfile(plugin_class)))
|
|
51
67
|
static_dir = os.path.join(mod_path, "static")
|
|
52
68
|
if not os.path.isdir(static_dir):
|
|
53
|
-
return None,
|
|
69
|
+
return None, []
|
|
70
|
+
|
|
71
|
+
# One manifest entry per capability the plugin provides. "capability" tells the
|
|
72
|
+
# frontend which handler mounts the component (overlay via MapOverlays, field via
|
|
73
|
+
# FieldRenderer, …) and "module" is that capability's Module Federation key.
|
|
74
|
+
bases = _frontend_capability_bases(plugin_class)
|
|
75
|
+
if not bases:
|
|
76
|
+
return None, []
|
|
54
77
|
|
|
55
78
|
bp = Blueprint(
|
|
56
79
|
f"plugin_{ep.name}",
|
|
@@ -65,15 +88,24 @@ def _register_plugin_static_resources(
|
|
|
65
88
|
response.headers["Access-Control-Allow-Origin"] = "*"
|
|
66
89
|
return response
|
|
67
90
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
|
|
91
|
+
entries = []
|
|
92
|
+
for base in bases:
|
|
93
|
+
# The capability token and its Module Federation key are derived from the base
|
|
94
|
+
# class name (MapOverlayPluginBase -> "MapOverlay" / "./MapOverlay"), so the
|
|
95
|
+
# class is the single source of truth — no separate identifier to keep in sync.
|
|
96
|
+
capability = base.__name__.removesuffix("PluginBase")
|
|
97
|
+
entries.append(
|
|
98
|
+
{
|
|
99
|
+
"pluginName": ep.name,
|
|
100
|
+
"url": f"/plugins/{ep.name}/static/remoteEntry.js",
|
|
101
|
+
"module": f"./{capability}",
|
|
102
|
+
"capability": capability,
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
return bp, entries
|
|
74
106
|
except Exception:
|
|
75
107
|
logger.warning("Failed to serve static files for plugin '%s'", ep.name)
|
|
76
|
-
return None,
|
|
108
|
+
return None, []
|
|
77
109
|
|
|
78
110
|
|
|
79
111
|
def _setup_location_model(
|
|
@@ -133,7 +165,15 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
133
165
|
|
|
134
166
|
locale_dir = os.path.join(directory, "locale")
|
|
135
167
|
config.translation_directories.append(locale_dir)
|
|
136
|
-
|
|
168
|
+
# Register goodmap's own plugin ecosystem with platzky: MapOverlayPluginBase is a
|
|
169
|
+
# host-defined capability, and goodmap plugins are discovered from the
|
|
170
|
+
# "goodmap.plugins" entry-point group. This makes them config-gated (is_active)
|
|
171
|
+
# through platzky's normal plugin loader, alongside platzky's own plugins.
|
|
172
|
+
app = platzky.create_app_from_config(
|
|
173
|
+
config,
|
|
174
|
+
extra_plugin_bases=list(CAPABILITY_BASES),
|
|
175
|
+
extra_plugins_entrypoints=[_PLUGIN_ENTRY_POINT_GROUP],
|
|
176
|
+
)
|
|
137
177
|
|
|
138
178
|
frontend_static_dir = os.path.join(directory, "static", "frontend")
|
|
139
179
|
app.register_blueprint(
|
|
@@ -160,12 +200,29 @@ def create_app_from_config(config: GoodmapConfig) -> platzky.Engine:
|
|
|
160
200
|
|
|
161
201
|
app.extensions["goodmap"] = {"location_obligatory_fields": location_obligatory_fields}
|
|
162
202
|
|
|
203
|
+
try:
|
|
204
|
+
plugins_data = app.db.get_plugins_data()
|
|
205
|
+
except Exception:
|
|
206
|
+
logger.warning(
|
|
207
|
+
"Could not read plugin config data; frontend plugins get empty config",
|
|
208
|
+
exc_info=True,
|
|
209
|
+
)
|
|
210
|
+
plugins_data = {}
|
|
211
|
+
|
|
163
212
|
plugin_manifest = []
|
|
164
213
|
for ep in importlib.metadata.entry_points(group=_PLUGIN_ENTRY_POINT_GROUP):
|
|
165
|
-
|
|
166
|
-
|
|
214
|
+
plugin_cfg = plugins_data.get(ep.name)
|
|
215
|
+
# Only serve the frontend for plugins that are explicitly enabled in config.
|
|
216
|
+
# platzky's loader has already instantiated the active ones (gated identically),
|
|
217
|
+
# so the manifest stays in lockstep with the loaded backend plugins.
|
|
218
|
+
if plugin_cfg is None or not plugin_cfg.is_active:
|
|
219
|
+
continue
|
|
220
|
+
bp, entries = _register_plugin_static_resources(ep)
|
|
221
|
+
if bp is not None and entries:
|
|
167
222
|
app.register_blueprint(bp)
|
|
168
|
-
|
|
223
|
+
for entry in entries:
|
|
224
|
+
entry["config"] = plugin_cfg.config
|
|
225
|
+
plugin_manifest.append(entry)
|
|
169
226
|
|
|
170
227
|
app.config["PLUGIN_MANIFEST"] = plugin_manifest
|
|
171
228
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Base class for goodmap map plugins."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from platzky.plugin.plugin import PluginBase
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GoodmapPluginBase(PluginBase):
|
|
9
|
+
"""Base class (family root) for goodmap plugin capabilities.
|
|
10
|
+
|
|
11
|
+
A goodmap plugin declares its frontend capabilities by subclassing the concrete
|
|
12
|
+
capability bases below (one or more). goodmap derives each capability's manifest
|
|
13
|
+
token and Module Federation module from the base class name — ``MapOverlayPluginBase``
|
|
14
|
+
-> capability ``"MapOverlay"`` exposed as ``"./MapOverlay"`` — so the class is the
|
|
15
|
+
single source of truth and there is no separate identifier to keep in sync.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
19
|
+
super().__init__(config)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MapOverlayPluginBase(GoodmapPluginBase):
|
|
23
|
+
"""Capability: a plugin that renders an overlay on top of the map view.
|
|
24
|
+
|
|
25
|
+
Map-overlay plugins contribute a frontend component (served via Module
|
|
26
|
+
Federation) that is mounted globally over the map — e.g. a banner shown when
|
|
27
|
+
no points are visible. They do not transform point/location data; for that,
|
|
28
|
+
use platzky's ``ContentTransformerPluginBase`` instead.
|
|
29
|
+
|
|
30
|
+
goodmap registers this capability with platzky (via ``extra_plugin_bases``)
|
|
31
|
+
so overlay plugins are config-gated through the standard plugin loader.
|
|
32
|
+
|
|
33
|
+
Manifest capability ``"MapOverlay"``; component exposed as ``"./MapOverlay"``.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
37
|
+
super().__init__(config)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class MarkerFieldPluginBase(GoodmapPluginBase):
|
|
41
|
+
"""Capability: a plugin that renders (or wraps) a marker-popup field.
|
|
42
|
+
|
|
43
|
+
Every field plugin is the same kind of thing — a *wrapper* in the field's rendering
|
|
44
|
+
fold, mounted by ``FieldRenderer``. Its ``config`` declares:
|
|
45
|
+
|
|
46
|
+
- ``field``: the field ``type`` it attaches to (e.g. ``"hyperlink"``, or a custom type
|
|
47
|
+
whose value the plugin's platzky shortcode produces as ``{"type": "<field>", ...}``).
|
|
48
|
+
- ``order`` (optional): its position in the stack — lower is more innermost; ties keep
|
|
49
|
+
registration order.
|
|
50
|
+
|
|
51
|
+
``FieldRenderer`` pipes the field's raw value through a chain of stages: the built-in for
|
|
52
|
+
the type (if any) renders it, then each plugin for that ``field`` transforms the result,
|
|
53
|
+
innermost-first. Each stage is ``({ input, config }) => element`` and receives the previous
|
|
54
|
+
stage's output as ``input`` — so the innermost stage gets the raw value and renders from
|
|
55
|
+
it, and every later stage gets the current element and wraps it. (A wrapping plugin thus
|
|
56
|
+
requires something to render the type: a built-in, or a renderer it ships with or depends
|
|
57
|
+
on; a type with only wrappers and no renderer is a misconfiguration.)
|
|
58
|
+
|
|
59
|
+
goodmap registers this capability with platzky (via ``extra_plugin_bases``) so field
|
|
60
|
+
plugins are config-gated through the standard plugin loader.
|
|
61
|
+
|
|
62
|
+
Manifest capability ``"MarkerField"``; component exposed as ``"./MarkerField"``.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(self, config: dict[str, Any]) -> None:
|
|
66
|
+
super().__init__(config)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# The frontend plugin capabilities goodmap defines: registered with platzky (so plugins
|
|
70
|
+
# subclassing them are recognised and config-gated) and used to derive each plugin's
|
|
71
|
+
# manifest entries. A plugin may subclass one or more of these.
|
|
72
|
+
CAPABILITY_BASES: tuple[type[GoodmapPluginBase], ...] = (
|
|
73
|
+
MapOverlayPluginBase,
|
|
74
|
+
MarkerFieldPluginBase,
|
|
75
|
+
)
|