streamlit-flexnav 0.1.0__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.
- streamlit_flexnav-0.1.0/LICENSE +21 -0
- streamlit_flexnav-0.1.0/PKG-INFO +131 -0
- streamlit_flexnav-0.1.0/README.md +112 -0
- streamlit_flexnav-0.1.0/pyproject.toml +41 -0
- streamlit_flexnav-0.1.0/setup.cfg +4 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/__init__.py +10 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/cli.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/configs/__init__.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/__init__.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/loader.py +106 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/menuregistry.py +29 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/models/__init__.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/models/accesscontrol.py +37 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/models/linktarget.py +7 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/models/menugroup.py +30 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/models/menustruct.py +153 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/models/rolemode.py +25 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/core/settings.py +64 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/images/__init__.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/logging_setup.py +59 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/menupages/__menu__.py +11 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/menupages/settings/settings_admin.py +16 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/menupages/settings/settings_user.py +16 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/__init__.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/__main__.py +164 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/cli.py +478 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/debug_paths.py +54 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/doctor.py +116 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/fix_keys.py +114 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/fix_streamlit_pages.py +115 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/linter.py +150 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/tools/startup_checks.py +46 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/ui/__init__.py +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/ui/converter.py +46 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/ui/navigator.py +120 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav/ui/session.py +5 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav.egg-info/PKG-INFO +131 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav.egg-info/SOURCES.txt +40 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav.egg-info/dependency_links.txt +1 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav.egg-info/entry_points.txt +2 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav.egg-info/requires.txt +6 -0
- streamlit_flexnav-0.1.0/src/streamlit_flexnav.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nednaz-IT
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamlit-flexnav
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Navigation toolkit, menu schema, and Streamlit integration
|
|
5
|
+
Author-email: Nednaz-IT <informatie@nednazit.nl>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/informatie/streamlit-flexnav
|
|
8
|
+
Project-URL: Source, https://github.com/informatie/streamlit-flexnav
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
13
|
+
Requires-Dist: pydantic>=2.12.5
|
|
14
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
15
|
+
Requires-Dist: streamlit>=1.53.1
|
|
16
|
+
Requires-Dist: structlog>=25.5.0
|
|
17
|
+
Requires-Dist: typer>=0.21.1
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# streamlit-flexnav
|
|
21
|
+
|
|
22
|
+
A modular, schema‑driven navigation framework for Streamlit applications.
|
|
23
|
+
`streamlit-flexnav` provides a clean, extensible way to define menus, pages, roles, and navigation behavior using YAML/JSON schemas — with automatic UI rendering, access control, and a powerful plugin‑ready architecture.
|
|
24
|
+
- [streamlit-flexnav](#streamlit-flexnav)
|
|
25
|
+
- [✨ Features](#-features)
|
|
26
|
+
- [📦 Installation](#-installation)
|
|
27
|
+
- [🚀 Quick Start](#-quick-start)
|
|
28
|
+
- [1. Create a menu schema (`menu.yaml`)](#1-create-a-menu-schema-menuyaml)
|
|
29
|
+
- [2. Load and register the schema](#2-load-and-register-the-schema)
|
|
30
|
+
- [🧭 Navigation Behavior](#-navigation-behavior)
|
|
31
|
+
- [⚙️ Configuration](#️-configuration)
|
|
32
|
+
- [🛠 CLI Tools](#-cli-tools)
|
|
33
|
+
- [📁 Project Structure](#-project-structure)
|
|
34
|
+
- [🧪 Development](#-development)
|
|
35
|
+
- [📄 License](#-license)
|
|
36
|
+
- [⭐ Acknowledgements](#-acknowledgements)
|
|
37
|
+
- [](#)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ✨ Features
|
|
42
|
+
|
|
43
|
+
- **Schema‑driven navigation** (YAML/JSON)
|
|
44
|
+
- **Automatic Streamlit UI rendering**
|
|
45
|
+
- **Role‑based access control**
|
|
46
|
+
- **Menu groups, pages, icons, and metadata**
|
|
47
|
+
- **Configurable sidebar behavior**
|
|
48
|
+
- **CLI tools for debugging, linting, and fixing schemas**
|
|
49
|
+
- **Plugin‑friendly architecture**
|
|
50
|
+
- **Fast, reproducible builds using uv**
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 📦 Installation
|
|
55
|
+
|
|
56
|
+
Install from PyPI:
|
|
57
|
+
```
|
|
58
|
+
pip install streamlit-flexnav
|
|
59
|
+
```
|
|
60
|
+
Or using uv:
|
|
61
|
+
```
|
|
62
|
+
uv add streamlit-flexnav
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
## 🚀 Quick Start
|
|
67
|
+
### 1. Create a menu schema (`menu.yaml`)
|
|
68
|
+
### 2. Load and register the schema
|
|
69
|
+
|
|
70
|
+
## 🧭 Navigation Behavior
|
|
71
|
+
Navigator automatically:<br>
|
|
72
|
+
Renders menu groups and pages<br>
|
|
73
|
+
Applies role‑based access control<br>
|
|
74
|
+
Highlights the active page<br>
|
|
75
|
+
Supports icons, dividers, and collapsible groups<br>
|
|
76
|
+
Integrates seamlessly with Streamlit’s session state
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
## ⚙️ Configuration
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
TODO
|
|
83
|
+
menu settings
|
|
84
|
+
Page settings
|
|
85
|
+
```
|
|
86
|
+
## 🛠 CLI Tools
|
|
87
|
+
After installation, the CLI becomes available:<br>
|
|
88
|
+
streamlit-flexnav
|
|
89
|
+
```
|
|
90
|
+
Command Description
|
|
91
|
+
doctor Diagnose common configuration issues
|
|
92
|
+
fix-keys Normalize schema keys
|
|
93
|
+
debug-paths Show resolved paths
|
|
94
|
+
linter Validate schema structure
|
|
95
|
+
startup-checks Run environment checks
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 📁 Project Structure
|
|
99
|
+
src/streamlit_flexnav/
|
|
100
|
+
core/ # Loaders, registries, settings, schema logic
|
|
101
|
+
ui/ # Streamlit UI components
|
|
102
|
+
tools/ # CLI tools
|
|
103
|
+
configs/ # Default configuration files
|
|
104
|
+
images/ # Icons and static assets
|
|
105
|
+
menupages/ # Built-in menu pages
|
|
106
|
+
|
|
107
|
+
See API_REFERENCE.md for full details.
|
|
108
|
+
|
|
109
|
+
## 🧪 Development
|
|
110
|
+
Clone the repository:
|
|
111
|
+
```
|
|
112
|
+
git clone https://github.com/informatie/streamlit-flexnav
|
|
113
|
+
cd streamlit-flexnav
|
|
114
|
+
```
|
|
115
|
+
Set up the environment:
|
|
116
|
+
```
|
|
117
|
+
uv venv
|
|
118
|
+
uv sync
|
|
119
|
+
source .venv/bin/activate
|
|
120
|
+
```
|
|
121
|
+
## 📄 License
|
|
122
|
+
MIT License — see LICENSE for details.
|
|
123
|
+
|
|
124
|
+
## ⭐ Acknowledgements
|
|
125
|
+
If you want, I can also generate:
|
|
126
|
+
|
|
127
|
+
TODO
|
|
128
|
+
|
|
129
|
+
Just tell me which one you want next.
|
|
130
|
+
|
|
131
|
+
##
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# streamlit-flexnav
|
|
2
|
+
|
|
3
|
+
A modular, schema‑driven navigation framework for Streamlit applications.
|
|
4
|
+
`streamlit-flexnav` provides a clean, extensible way to define menus, pages, roles, and navigation behavior using YAML/JSON schemas — with automatic UI rendering, access control, and a powerful plugin‑ready architecture.
|
|
5
|
+
- [streamlit-flexnav](#streamlit-flexnav)
|
|
6
|
+
- [✨ Features](#-features)
|
|
7
|
+
- [📦 Installation](#-installation)
|
|
8
|
+
- [🚀 Quick Start](#-quick-start)
|
|
9
|
+
- [1. Create a menu schema (`menu.yaml`)](#1-create-a-menu-schema-menuyaml)
|
|
10
|
+
- [2. Load and register the schema](#2-load-and-register-the-schema)
|
|
11
|
+
- [🧭 Navigation Behavior](#-navigation-behavior)
|
|
12
|
+
- [⚙️ Configuration](#️-configuration)
|
|
13
|
+
- [🛠 CLI Tools](#-cli-tools)
|
|
14
|
+
- [📁 Project Structure](#-project-structure)
|
|
15
|
+
- [🧪 Development](#-development)
|
|
16
|
+
- [📄 License](#-license)
|
|
17
|
+
- [⭐ Acknowledgements](#-acknowledgements)
|
|
18
|
+
- [](#)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## ✨ Features
|
|
23
|
+
|
|
24
|
+
- **Schema‑driven navigation** (YAML/JSON)
|
|
25
|
+
- **Automatic Streamlit UI rendering**
|
|
26
|
+
- **Role‑based access control**
|
|
27
|
+
- **Menu groups, pages, icons, and metadata**
|
|
28
|
+
- **Configurable sidebar behavior**
|
|
29
|
+
- **CLI tools for debugging, linting, and fixing schemas**
|
|
30
|
+
- **Plugin‑friendly architecture**
|
|
31
|
+
- **Fast, reproducible builds using uv**
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 📦 Installation
|
|
36
|
+
|
|
37
|
+
Install from PyPI:
|
|
38
|
+
```
|
|
39
|
+
pip install streamlit-flexnav
|
|
40
|
+
```
|
|
41
|
+
Or using uv:
|
|
42
|
+
```
|
|
43
|
+
uv add streamlit-flexnav
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
## 🚀 Quick Start
|
|
48
|
+
### 1. Create a menu schema (`menu.yaml`)
|
|
49
|
+
### 2. Load and register the schema
|
|
50
|
+
|
|
51
|
+
## 🧭 Navigation Behavior
|
|
52
|
+
Navigator automatically:<br>
|
|
53
|
+
Renders menu groups and pages<br>
|
|
54
|
+
Applies role‑based access control<br>
|
|
55
|
+
Highlights the active page<br>
|
|
56
|
+
Supports icons, dividers, and collapsible groups<br>
|
|
57
|
+
Integrates seamlessly with Streamlit’s session state
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
## ⚙️ Configuration
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
TODO
|
|
64
|
+
menu settings
|
|
65
|
+
Page settings
|
|
66
|
+
```
|
|
67
|
+
## 🛠 CLI Tools
|
|
68
|
+
After installation, the CLI becomes available:<br>
|
|
69
|
+
streamlit-flexnav
|
|
70
|
+
```
|
|
71
|
+
Command Description
|
|
72
|
+
doctor Diagnose common configuration issues
|
|
73
|
+
fix-keys Normalize schema keys
|
|
74
|
+
debug-paths Show resolved paths
|
|
75
|
+
linter Validate schema structure
|
|
76
|
+
startup-checks Run environment checks
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 📁 Project Structure
|
|
80
|
+
src/streamlit_flexnav/
|
|
81
|
+
core/ # Loaders, registries, settings, schema logic
|
|
82
|
+
ui/ # Streamlit UI components
|
|
83
|
+
tools/ # CLI tools
|
|
84
|
+
configs/ # Default configuration files
|
|
85
|
+
images/ # Icons and static assets
|
|
86
|
+
menupages/ # Built-in menu pages
|
|
87
|
+
|
|
88
|
+
See API_REFERENCE.md for full details.
|
|
89
|
+
|
|
90
|
+
## 🧪 Development
|
|
91
|
+
Clone the repository:
|
|
92
|
+
```
|
|
93
|
+
git clone https://github.com/informatie/streamlit-flexnav
|
|
94
|
+
cd streamlit-flexnav
|
|
95
|
+
```
|
|
96
|
+
Set up the environment:
|
|
97
|
+
```
|
|
98
|
+
uv venv
|
|
99
|
+
uv sync
|
|
100
|
+
source .venv/bin/activate
|
|
101
|
+
```
|
|
102
|
+
## 📄 License
|
|
103
|
+
MIT License — see LICENSE for details.
|
|
104
|
+
|
|
105
|
+
## ⭐ Acknowledgements
|
|
106
|
+
If you want, I can also generate:
|
|
107
|
+
|
|
108
|
+
TODO
|
|
109
|
+
|
|
110
|
+
Just tell me which one you want next.
|
|
111
|
+
|
|
112
|
+
##
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# --------------------------------------------------------------------
|
|
2
|
+
# Patch: bugfix → 0.1.1
|
|
3
|
+
# Minor: new features → 0.2.0
|
|
4
|
+
# Major: breaking changes → 1.0.0
|
|
5
|
+
# --------------------------------------------------------------------
|
|
6
|
+
[project]
|
|
7
|
+
name = "streamlit-flexnav"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "Navigation toolkit, menu schema, and Streamlit integration"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
license = { text = "MIT" }
|
|
13
|
+
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Nednaz-IT", email = "informatie@nednazit.nl" }
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
dependencies = [
|
|
19
|
+
"python-dotenv>=1.0.0",
|
|
20
|
+
"pydantic>=2.12.5",
|
|
21
|
+
"pyyaml>=6.0.3",
|
|
22
|
+
"streamlit>=1.53.1",
|
|
23
|
+
"structlog>=25.5.0",
|
|
24
|
+
"typer>=0.21.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
streamlit-flexnav = "streamlit_flexnav.tools.cli:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/informatie/streamlit-flexnav"
|
|
32
|
+
Source = "https://github.com/informatie/streamlit-flexnav"
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["streamlit_flexnav"]
|
|
36
|
+
sources = ["src"]
|
|
37
|
+
|
|
38
|
+
[tool.hatch.build]
|
|
39
|
+
include = [
|
|
40
|
+
"src/streamlit_flexnav/**/*",
|
|
41
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import importlib.util
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
8
|
+
from streamlit_flexnav.core.settings import MENUPAGES_ROOT
|
|
9
|
+
from streamlit_flexnav.core.models.menustruct import MenuStruct
|
|
10
|
+
from streamlit_flexnav.core.models.menugroup import MenuGroup
|
|
11
|
+
|
|
12
|
+
log = structlog.get_logger().bind(component="loader")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _load_module_from_path(path: Path):
|
|
16
|
+
spec = importlib.util.spec_from_file_location(path.stem, path)
|
|
17
|
+
module = importlib.util.module_from_spec(spec)
|
|
18
|
+
sys.modules[path.stem] = module
|
|
19
|
+
spec.loader.exec_module(module)
|
|
20
|
+
return module
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def load_menupages() -> list[MenuStruct]:
|
|
24
|
+
"""
|
|
25
|
+
Scan MENUPAGES_ROOT for .py files and extract PAGE = MenuStruct(...) definitions.
|
|
26
|
+
Folder structure is flattened into a single group name:
|
|
27
|
+
folder1/folder2/folder3 → "folder1_folder2_folder3"
|
|
28
|
+
"""
|
|
29
|
+
results: list[MenuStruct] = []
|
|
30
|
+
|
|
31
|
+
for file in MENUPAGES_ROOT.rglob("*.py"):
|
|
32
|
+
if file.name.startswith("_"):
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
module = _load_module_from_path(file)
|
|
37
|
+
|
|
38
|
+
if not hasattr(module, "PAGE"):
|
|
39
|
+
log.warning("missing_PAGE_struct", file=str(file))
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
page_struct = module.PAGE
|
|
43
|
+
|
|
44
|
+
if not isinstance(page_struct, MenuStruct):
|
|
45
|
+
log.error("invalid_PAGE_type", file=str(file))
|
|
46
|
+
continue
|
|
47
|
+
|
|
48
|
+
# Inject absolute path
|
|
49
|
+
page_struct.path = str(file.resolve())
|
|
50
|
+
|
|
51
|
+
# Compute flattened group name
|
|
52
|
+
relative = file.relative_to(MENUPAGES_ROOT)
|
|
53
|
+
parts = relative.parts[:-1] # folders only
|
|
54
|
+
|
|
55
|
+
group = "_".join(parts) if parts else None
|
|
56
|
+
page_struct.group = group
|
|
57
|
+
|
|
58
|
+
results.append(page_struct)
|
|
59
|
+
|
|
60
|
+
except Exception as e:
|
|
61
|
+
log.exception("failed_loading_page", file=str(file), error=str(e))
|
|
62
|
+
|
|
63
|
+
log.info("menupages_loaded", count=len(results))
|
|
64
|
+
return results
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def load_menugroups() -> list[MenuGroup]:
|
|
69
|
+
"""
|
|
70
|
+
Discover all MenuGroup definitions under MENUPAGES_ROOT.
|
|
71
|
+
|
|
72
|
+
Expected pattern inside each module:
|
|
73
|
+
MENU = MenuGroup(...)
|
|
74
|
+
"""
|
|
75
|
+
results: list[MenuGroup] = []
|
|
76
|
+
|
|
77
|
+
for file in MENUPAGES_ROOT.rglob("*.py"):
|
|
78
|
+
if not file.name.startswith("__menu__"):
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
module = _load_module_from_path(file)
|
|
83
|
+
|
|
84
|
+
menu_struct = getattr(module, "MENU", None)
|
|
85
|
+
if menu_struct is None:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if not isinstance(menu_struct, MenuGroup):
|
|
89
|
+
log.error("invalid_MENU_type", file=str(file))
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
# Inject provenance
|
|
93
|
+
menu_struct.path = str(file.resolve())
|
|
94
|
+
|
|
95
|
+
results.append(menu_struct)
|
|
96
|
+
|
|
97
|
+
except Exception as e:
|
|
98
|
+
log.exception(
|
|
99
|
+
"failed_loading_menugroup",
|
|
100
|
+
file=str(file),
|
|
101
|
+
error=str(e),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
log.info("menugroups_loaded", count=len(results))
|
|
105
|
+
return results
|
|
106
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import structlog
|
|
4
|
+
from streamlit_flexnav.core.loader import load_menupages
|
|
5
|
+
from streamlit_flexnav.ui.converter import MenuStruct
|
|
6
|
+
|
|
7
|
+
log = structlog.get_logger().bind(component="menuregistry")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MenuRegistry:
|
|
11
|
+
"""
|
|
12
|
+
Holds the loaded MenuStruct objects.
|
|
13
|
+
Reloadable, but not manually mutable.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._items: list[MenuStruct] = []
|
|
18
|
+
self.reload()
|
|
19
|
+
|
|
20
|
+
def reload(self):
|
|
21
|
+
self._items = load_menupages()
|
|
22
|
+
log.info("menuregistry_reloaded", count=len(self._items))
|
|
23
|
+
|
|
24
|
+
def all(self) -> list[MenuStruct]:
|
|
25
|
+
return self._items
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Singleton
|
|
29
|
+
menuregistry = MenuRegistry()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
from pydantic import BaseModel, Field, model_validator
|
|
3
|
+
from .rolemode import RoleMode
|
|
4
|
+
|
|
5
|
+
class AccessControl(BaseModel):
|
|
6
|
+
"""
|
|
7
|
+
Role-based access control for a menu item.
|
|
8
|
+
Ensures no overlap between allowed and denied roles.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
allowed_roles: List[RoleMode] = Field(
|
|
12
|
+
default_factory=list,
|
|
13
|
+
description="Roles that may see/use this menu item."
|
|
14
|
+
)
|
|
15
|
+
denied_roles: List[RoleMode] = Field(
|
|
16
|
+
default_factory=list,
|
|
17
|
+
description="Roles that must never see/use this menu item."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
@model_validator(mode="after")
|
|
21
|
+
def validate_no_overlap(self):
|
|
22
|
+
overlap = set(self.allowed_roles) & set(self.denied_roles)
|
|
23
|
+
if overlap:
|
|
24
|
+
names = ", ".join(r.display_name() for r in overlap)
|
|
25
|
+
raise ValueError(f"Role(s) cannot be both allowed and denied: {names}")
|
|
26
|
+
return self
|
|
27
|
+
|
|
28
|
+
def is_allowed(self, role: RoleMode) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Returns True if the given role is permitted.
|
|
31
|
+
Denied roles always override allowed roles.
|
|
32
|
+
"""
|
|
33
|
+
if role in self.denied_roles:
|
|
34
|
+
return False
|
|
35
|
+
if self.allowed_roles and role not in self.allowed_roles:
|
|
36
|
+
return False
|
|
37
|
+
return True
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from pydantic import BaseModel, Field
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MenuGroup(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Represents a styled menu/submenu section.
|
|
9
|
+
This is NOT a page. It is a visual grouping.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
key: str = Field(..., description="Unique group identifier")
|
|
13
|
+
label: str = Field(..., description="Displayed group label")
|
|
14
|
+
|
|
15
|
+
order: int = Field(
|
|
16
|
+
default=100,
|
|
17
|
+
ge=-10_000,
|
|
18
|
+
le=10_000,
|
|
19
|
+
description="Ordering in of menu",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Styling
|
|
23
|
+
icon: Optional[str] = None
|
|
24
|
+
color: Optional[str] = None
|
|
25
|
+
bold: bool = False
|
|
26
|
+
italic: bool = False
|
|
27
|
+
size: Optional[int] = None # 1–3 simulated sizes
|
|
28
|
+
|
|
29
|
+
# Pages inside this group
|
|
30
|
+
pages: List[object] = Field(default_factory=list)
|