moosey-cms 0.3.0__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.
- moosey_cms/.python-version +1 -0
- moosey_cms/__init__.py +12 -0
- moosey_cms/cache.py +68 -0
- moosey_cms/file_watcher.py +31 -0
- moosey_cms/filters.py +522 -0
- moosey_cms/helpers.py +313 -0
- moosey_cms/hot_reload_script.py +91 -0
- moosey_cms/main.py +283 -0
- moosey_cms/md.py +192 -0
- moosey_cms/models.py +73 -0
- moosey_cms/py.typed +0 -0
- moosey_cms/pyproject.toml +28 -0
- moosey_cms/seo.py +153 -0
- moosey_cms/static/js/reload-script.js +77 -0
- moosey_cms-0.3.0.dist-info/METADATA +295 -0
- moosey_cms-0.3.0.dist-info/RECORD +17 -0
- moosey_cms-0.3.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: moosey-cms
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Requires-Dist: cachetools>=6.2.4
|
|
7
|
+
Requires-Dist: fastapi>=0.128.0
|
|
8
|
+
Requires-Dist: inflection>=0.5.1
|
|
9
|
+
Requires-Dist: jinja2>=3.1.6
|
|
10
|
+
Requires-Dist: markdown>=3.10
|
|
11
|
+
Requires-Dist: pymdown-extensions>=10.20
|
|
12
|
+
Requires-Dist: python-frontmatter>=1.1.0
|
|
13
|
+
Requires-Dist: python-slugify>=8.0.4
|
|
14
|
+
Requires-Dist: uvicorn>=0.40.0
|
|
15
|
+
Requires-Dist: watchdog>=6.0.0
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
<!--
|
|
19
|
+
Copyright (c) 2026 Anthony Mugendi
|
|
20
|
+
|
|
21
|
+
This software is released under the MIT License.
|
|
22
|
+
https://opensource.org/licenses/MIT
|
|
23
|
+
-->
|
|
24
|
+
|
|
25
|
+
# Moosey CMS π«
|
|
26
|
+
|
|
27
|
+
**A lightweight, drop-in Markdown CMS for FastAPI.**
|
|
28
|
+
|
|
29
|
+
Moosey CMS transforms your FastAPI application into a content-driven website without the need for a database. It bridges the gap between static site generators and dynamic web servers, offering hot-reloading, intelligent caching, SEO management, and a powerful templating hierarchy.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+

|
|
34
|
+
|
|
35
|
+
Check out the [/example](/example/) for templating and content samples used to generate the images above.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## π Features
|
|
40
|
+
|
|
41
|
+
* **No Database Required:** Content is managed via Markdown files with YAML Frontmatter.
|
|
42
|
+
* **Intelligent Routing:** URL paths automatically map to your content directory structure.
|
|
43
|
+
* **Smart Templating:** "Waterfall" inheritance logic (Singular/Plural) to automatically find the best layout for every page.
|
|
44
|
+
* **Hot Reloading:** Instant browser refresh when Content or Templates change (Development mode only).
|
|
45
|
+
* **High Performance:** Built-in caching (TTL-based) that auto-clears on file changes.
|
|
46
|
+
* **SEO Ready:** Automatic OpenGraph, Twitter Cards, JSON-LD, and Meta tags generation.
|
|
47
|
+
* **Rich Markdown:** Supports tables, emojis, task lists, and syntax highlighting out of the box.
|
|
48
|
+
* **Jinja2 Power:** Use Jinja2 logic directly inside your Markdown files (Securely Sandboxed).
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## π¦ Installation
|
|
53
|
+
|
|
54
|
+
### Using UV (Recommended)
|
|
55
|
+
```bash
|
|
56
|
+
uv add moosey-cms
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Using Pip
|
|
60
|
+
```bash
|
|
61
|
+
pip install moosey-cms
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## β‘ Quick Start
|
|
67
|
+
|
|
68
|
+
Integrate Moosey CMS into your existing FastAPI app in just a few lines.
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from fastapi import FastAPI
|
|
72
|
+
from fastapi.staticfiles import StaticFiles
|
|
73
|
+
from pathlib import Path
|
|
74
|
+
from moosey_cms import init_cms
|
|
75
|
+
|
|
76
|
+
app = FastAPI()
|
|
77
|
+
|
|
78
|
+
# 1. Define your paths
|
|
79
|
+
BASE_DIR = Path(__file__).resolve().parent
|
|
80
|
+
CONTENT_DIR = BASE_DIR / "content"
|
|
81
|
+
TEMPLATES_DIR = BASE_DIR / "templates"
|
|
82
|
+
|
|
83
|
+
# 2. Mount static files (Optional, but recommended for CSS/Images)
|
|
84
|
+
app.mount("/static", StaticFiles(directory="static"), name="static")
|
|
85
|
+
|
|
86
|
+
# 3. Initialize the CMS
|
|
87
|
+
init_cms(
|
|
88
|
+
app,
|
|
89
|
+
host="localhost",
|
|
90
|
+
port=8000,
|
|
91
|
+
dirs={
|
|
92
|
+
"content": CONTENT_DIR,
|
|
93
|
+
"templates": TEMPLATES_DIR
|
|
94
|
+
},
|
|
95
|
+
mode="development", # Enables hot-reloading
|
|
96
|
+
site_data={
|
|
97
|
+
"name": "My Awesome Site",
|
|
98
|
+
"description": "A site built with Moosey CMS",
|
|
99
|
+
"author": "Jane Doe",
|
|
100
|
+
"keywords": ["fastapi", "cms", "python"],
|
|
101
|
+
"open_graph": {
|
|
102
|
+
"og_image": "/static/cover.jpg"
|
|
103
|
+
},
|
|
104
|
+
"social": {
|
|
105
|
+
"twitter": "https://x.com/myhandle",
|
|
106
|
+
"github": "https://github.com/myhandle"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## π Directory Structure
|
|
115
|
+
|
|
116
|
+
Moosey CMS relies on a convention-over-configuration file structure.
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
.
|
|
120
|
+
βββ main.py
|
|
121
|
+
βββ content/ <-- Your Markdown Files
|
|
122
|
+
β βββ index.md <-- Homepage (/)
|
|
123
|
+
β βββ about.md <-- About Page (/about)
|
|
124
|
+
β βββ blog/
|
|
125
|
+
β βββ index.md <-- Blog Listing (/blog)
|
|
126
|
+
β βββ post-1.md <-- Blog Post (/blog/post-1)
|
|
127
|
+
β βββ post-2.md
|
|
128
|
+
βββ templates/
|
|
129
|
+
βββ layout
|
|
130
|
+
βββ base.html <-- Base layout
|
|
131
|
+
βββ index.html <-- Home Page layout
|
|
132
|
+
βββ page.html <-- Default fallback
|
|
133
|
+
βββ blog.html <-- Layout for /blog (Listing)
|
|
134
|
+
βββ post.html <-- Layout for /blog/post-1 (Single Item)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## π¨ Templating Logic (The Waterfall)
|
|
140
|
+
|
|
141
|
+
When a user visits a URL, Moosey CMS searches for templates in a specific cascading order. This allows you to set global defaults while retaining the ability to customize specific pages or sections.
|
|
142
|
+
|
|
143
|
+
**Example Scenario:**
|
|
144
|
+
A user visits **`/posts/post-1`**.
|
|
145
|
+
|
|
146
|
+
**Directory Structure:**
|
|
147
|
+
|
|
148
|
+
```text
|
|
149
|
+
.
|
|
150
|
+
βββ content/
|
|
151
|
+
β βββ posts/
|
|
152
|
+
β βββ index.md <-- Required for the '/posts' listing page to work
|
|
153
|
+
β βββ post-1.md <-- The article being requested
|
|
154
|
+
β βββ post-2.md
|
|
155
|
+
βββ templates/
|
|
156
|
+
βββ posts/
|
|
157
|
+
β βββ post-1.html <-- 1. Specific Override
|
|
158
|
+
βββ post.html <-- 2. Singular (Item) Layout
|
|
159
|
+
βββ posts.html <-- 3. Plural (Section) Layout
|
|
160
|
+
βββ page.html <-- 4. Global Fallback
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Resolution Order:**
|
|
164
|
+
|
|
165
|
+
1. **Frontmatter Override:** If `post-1.md` contains `template: special.html`, that template is used immediately.
|
|
166
|
+
2. **Exact Match:** `templates/posts/post-1.html`.
|
|
167
|
+
3. **Singular Parent:** `templates/post.html` (Perfect for generic blog posts).
|
|
168
|
+
4. **Plural Parent:** `templates/posts.html` (Perfect for section indexes).
|
|
169
|
+
5. **Fallback:** `templates/page.html`.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## π Frontmatter Configuration
|
|
174
|
+
|
|
175
|
+
You can control routing, visibility, and layout directly from the Markdown file YAML frontmatter.
|
|
176
|
+
|
|
177
|
+
### Basic Metadata
|
|
178
|
+
```yaml
|
|
179
|
+
title: My Amazing Post
|
|
180
|
+
date: 2024-01-01
|
|
181
|
+
description: A short summary for SEO.
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Organization & Navigation
|
|
185
|
+
| Key | Type | Description |
|
|
186
|
+
| :--- | :--- | :--- |
|
|
187
|
+
| `order` | `int` | Sort order in sidebars. Lower numbers appear first. Default: `9999`. |
|
|
188
|
+
| `nav_title` | `str` | Short title to display in sidebars (if different from `title`). |
|
|
189
|
+
| `visible` | `bool` | Set to `false` to hide from sidebars/menus (page remains accessible via URL). |
|
|
190
|
+
| `draft` | `bool` | If `true`, the page is only visible in `development` mode. |
|
|
191
|
+
| `group` | `str` | Group sidebar items under a heading (requires template support). |
|
|
192
|
+
|
|
193
|
+
### Advanced Routing
|
|
194
|
+
| Key | Type | Description |
|
|
195
|
+
| :--- | :--- | :--- |
|
|
196
|
+
| `template` | `str` | Force a specific template file (e.g., `template: landing.html`). |
|
|
197
|
+
| `external_link` | `str` | The sidebar link will point to this external URL instead of the page itself. |
|
|
198
|
+
| `redirect` | `str` | Alias for `external_link`. |
|
|
199
|
+
|
|
200
|
+
**Example:**
|
|
201
|
+
```yaml
|
|
202
|
+
---
|
|
203
|
+
title: API Documentation
|
|
204
|
+
nav_title: API Docs
|
|
205
|
+
weight: 1
|
|
206
|
+
group: "Developer Tools"
|
|
207
|
+
external_link: "https://api.mysite.com"
|
|
208
|
+
---
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## π§© Custom Filters & Logic
|
|
214
|
+
|
|
215
|
+
Moosey CMS comes packed with a comprehensive library of Jinja2 filters to help you format your data effortlessly.
|
|
216
|
+
|
|
217
|
+
### Date & Time
|
|
218
|
+
| Filter | Usage | Output |
|
|
219
|
+
| :--- | :--- | :--- |
|
|
220
|
+
| `fancy_date` | <code>{{ date | fancy_date }}</code> | 13th Jan, 2026 at 6:00 PM |
|
|
221
|
+
| `short_date` | <code>{{ date | short_date }}</code> | Jan 13, 2026 |
|
|
222
|
+
| `iso_date` | <code>{{ date | iso_date }}</code> | 2026-01-13 |
|
|
223
|
+
| `time_only` | <code>{{ date | time_only }}</code> | 6:00 PM |
|
|
224
|
+
| `relative_time` | <code>{{ date | relative_time }}</code> | 2 hours ago / yesterday |
|
|
225
|
+
|
|
226
|
+
### Currency & Numbers
|
|
227
|
+
| Filter | Usage | Output |
|
|
228
|
+
| :--- | :--- | :--- |
|
|
229
|
+
| `currency` | <code>{{ 1234.5 | currency('USD') }}</code> | $1,234.50 |
|
|
230
|
+
| `compact_currency` | <code>{{ 1500000 | compact_currency }}</code> | $1.5M |
|
|
231
|
+
| `currency_name` | <code>{{ 'KES' | currency_name }}</code> | Kenyan Shilling |
|
|
232
|
+
| `number_format` | <code>{{ 1000 | number_format }}</code> | 1,000 |
|
|
233
|
+
| `percentage` | <code>{{ 50.5 | percentage }}</code> | 50.5% |
|
|
234
|
+
| `ordinal` | <code>{{ 3 | ordinal }}</code> | 3rd |
|
|
235
|
+
|
|
236
|
+
### Geography & Locale
|
|
237
|
+
| Filter | Usage | Output |
|
|
238
|
+
| :--- | :--- | :--- |
|
|
239
|
+
| `country_flag` | <code>{{ 'US' | country_flag }}</code> | πΊπΈ |
|
|
240
|
+
| `country_name` | <code>{{ 'DE' | country_name }}</code> | Germany |
|
|
241
|
+
| `language_name` | <code>{{ 'fr' | language_name }}</code> | French |
|
|
242
|
+
|
|
243
|
+
### Text Formatting
|
|
244
|
+
| Filter | Usage | Output |
|
|
245
|
+
| :--- | :--- | :--- |
|
|
246
|
+
| `truncate_words` | <code>{{ text | truncate_words(10) }}</code> | Truncates text to 10 words... |
|
|
247
|
+
| `excerpt` | <code>{{ text | excerpt(150) }}</code> | Smart excerpt breaking at sentences. |
|
|
248
|
+
| `read_time` | <code>{{ content | read_time }}</code> | 5 min read |
|
|
249
|
+
| `slugify` | <code>{{ 'Hello World' | slugify }}</code> | hello-world |
|
|
250
|
+
| `title_case` | <code>{{ 'a tale of two cities' | title_case }}</code> | A Tale of Two Cities |
|
|
251
|
+
| `smart_quotes` | <code>{{ '"Hello"' | smart_quotes }}</code> | βHelloβ |
|
|
252
|
+
|
|
253
|
+
### Utilities
|
|
254
|
+
| Filter | Usage | Output |
|
|
255
|
+
| :--- | :--- | :--- |
|
|
256
|
+
| `filesize` | <code>{{ 1024 | filesize }}</code> | 1.0 KB |
|
|
257
|
+
| `yesno` | <code>{{ True | yesno }}</code> | Yes |
|
|
258
|
+
| `default_if_none` | <code>{{ val | default_if_none('N/A') }}</code> | Returns default if None |
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## βοΈ Configuration Reference
|
|
263
|
+
|
|
264
|
+
The `init_cms` function accepts the following parameters:
|
|
265
|
+
|
|
266
|
+
| Parameter | Type | Description |
|
|
267
|
+
| :--- | :--- | :--- |
|
|
268
|
+
| `app` | `FastAPI` | Your FastAPI application instance. |
|
|
269
|
+
| `host` | `str` | Server host (used for hot-reload script injection). |
|
|
270
|
+
| `port` | `int` | Server port. |
|
|
271
|
+
| `dirs` | `dict` | Dictionary containing `content` and `templates` Paths. |
|
|
272
|
+
| `mode` | `str` | `"development"` (enables hot reload/no cache) or `"production"`. |
|
|
273
|
+
| `site_data` | `dict` | Global data (Name, Author, Social Links). |
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## π‘οΈ Security & Mitigation
|
|
278
|
+
|
|
279
|
+
Moosey CMS takes security seriously. We have implemented several layers of protection to ensure your site remains safe:
|
|
280
|
+
|
|
281
|
+
1. **Path Traversal Protection:** All URL requests are securely resolved against the content root using strict `pathlib` checks. It is impossible to access files outside the `content` directory (e.g., `../../etc/passwd`).
|
|
282
|
+
2. **SSTI Sandbox:** While we allow Jinja2 logic inside Markdown files, this is executed in a **Sandboxed Environment**. Dangerous attributes (like `__class__`, `__subclasses__`) are stripped, preventing Remote Code Execution (RCE) attacks.
|
|
283
|
+
3. **DoS Prevention:** The Hot-Reload middleware includes size checks to prevent memory exhaustion attacks from large file uploads/downloads.
|
|
284
|
+
|
|
285
|
+
### π Bug Reporting
|
|
286
|
+
Security is an ongoing process. If you discover a vulnerability, bug, or potential risk, please **open an issue** on our GitHub repository immediately. We appreciate community feedback to keep Moosey secure for everyone.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Gratitude
|
|
291
|
+
This project is inspired by [fastapi-blog](https://github.com/pydanny/fastapi-blog) by [Daniel](https://github.com/pydanny). Initially, I wanted to use **fastapi-blog** and it worked really well till I needed features like hot-reloading.
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
MIT License. Copyright (c) 2026 Anthony Mugendi.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
moosey_cms/.python-version,sha256=e1X45ntWI8S-8_ppEojalDfXnTq6FW3kjUgdsyrH0W0,5
|
|
2
|
+
moosey_cms/__init__.py,sha256=y7gzxC1LB7qRmjqJHJpN4kEqBNAbuIwNc4xeEI2clMY,184
|
|
3
|
+
moosey_cms/cache.py,sha256=YI6rRb4OVi-Mb1CmMW-jRz0CC9U6YZyszqLmjqLOsq8,2067
|
|
4
|
+
moosey_cms/file_watcher.py,sha256=ViadvrDD2y0MN9VJlz9Kkp31qFkYBhDCoK7xtdA4tAY,923
|
|
5
|
+
moosey_cms/filters.py,sha256=QIHeffZAxn4KqQE4zwR4D7njE96L-oeqHc5DYrYgCpw,15983
|
|
6
|
+
moosey_cms/helpers.py,sha256=m94mDAaDKiH6wvzqoICQuavJ53EVTJ5VmJiRsfLT38o,10277
|
|
7
|
+
moosey_cms/hot_reload_script.py,sha256=Dflj5hgHVkVOfjeU7wzEUeVTt684nj22et8jKzVFEGw,2987
|
|
8
|
+
moosey_cms/main.py,sha256=VDRbhmGUpyar8ZY4Dv33YQ1_bQX-vkgQFNgN_ckXqY4,8926
|
|
9
|
+
moosey_cms/md.py,sha256=m857SKApJkK62wNrMVsypuJAqumbBt5GuPvcnuN1O6w,4970
|
|
10
|
+
moosey_cms/models.py,sha256=kYNIf7utTq94PUdqqZXfil5vpp0wciI5UgqRHJ56A0E,3070
|
|
11
|
+
moosey_cms/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
moosey_cms/pyproject.toml,sha256=lFRsY2yJOKtDWJc-Mfq9s0teGrhmB3ALDJyMltjBOGg,735
|
|
13
|
+
moosey_cms/seo.py,sha256=jQ2FVuELNoytJkbp0ILK_IF7sZSaz9fkl59HM7xlg70,5246
|
|
14
|
+
moosey_cms/static/js/reload-script.js,sha256=hnrVXEWeTK-Y2vLeADmtlZ7fOXpDJMF-0zK09o3mrOA,2247
|
|
15
|
+
moosey_cms-0.3.0.dist-info/METADATA,sha256=QbhE3Wg-XFHAQNRF4pe6kK8BjyVNfIXgoBR0KLyv3BY,10899
|
|
16
|
+
moosey_cms-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
17
|
+
moosey_cms-0.3.0.dist-info/RECORD,,
|