platzky 0.4.1__tar.gz → 0.4.3__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.
- {platzky-0.4.1 → platzky-0.4.3}/PKG-INFO +1 -1
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/db.py +9 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/graph_ql_db.py +15 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/json_db.py +8 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/mongodb_db.py +8 -0
- platzky-0.4.3/platzky/engine.py +140 -0
- {platzky-0.4.1 → platzky-0.4.3}/pyproject.toml +10 -6
- platzky-0.4.1/platzky/engine.py +0 -71
- {platzky-0.4.1 → platzky-0.4.3}/README.md +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/__init__.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/admin/admin.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/admin/fake_login.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/admin/templates/admin.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/admin/templates/login.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/admin/templates/module.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/blog/__init__.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/blog/blog.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/blog/comment_form.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/config.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/README.md +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/__init__.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/db_loader.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/github_json_db.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/google_json_db.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/db/json_file_db.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/locale/en/LC_MESSAGES/messages.po +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/locale/pl/LC_MESSAGES/messages.po +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/models.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/platzky.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/plugin/plugin.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/plugin/plugin_loader.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/seo/seo.py +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/static/blog.css +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/static/styles.css +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/404.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/base.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/blog.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/body_meta.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/dynamic_css.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/feed.xml +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/head_meta.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/page.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/post.html +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/robots.txt +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/templates/sitemap.xml +0 -0
- {platzky-0.4.1 → platzky-0.4.3}/platzky/www_handler.py +0 -0
|
@@ -96,6 +96,15 @@ class DB(ABC):
|
|
|
96
96
|
def get_font(self) -> str:
|
|
97
97
|
pass
|
|
98
98
|
|
|
99
|
+
@abstractmethod
|
|
100
|
+
def health_check(self) -> None:
|
|
101
|
+
"""Perform a health check on the database.
|
|
102
|
+
|
|
103
|
+
Should raise an exception if the database is not healthy.
|
|
104
|
+
This should be a lightweight operation suitable for health checks.
|
|
105
|
+
"""
|
|
106
|
+
pass
|
|
107
|
+
|
|
99
108
|
|
|
100
109
|
class DBConfig(BaseModel):
|
|
101
110
|
type: str = Field(alias="TYPE")
|
|
@@ -307,3 +307,18 @@ class GraphQL(DB):
|
|
|
307
307
|
"""
|
|
308
308
|
)
|
|
309
309
|
return self.client.execute(plugins_data)["pluginConfigs"]
|
|
310
|
+
|
|
311
|
+
def health_check(self) -> None:
|
|
312
|
+
"""Perform a health check on the GraphQL database.
|
|
313
|
+
|
|
314
|
+
Raises an exception if the database is not accessible.
|
|
315
|
+
"""
|
|
316
|
+
# Simple query to check connectivity
|
|
317
|
+
health_query = gql(
|
|
318
|
+
"""
|
|
319
|
+
query {
|
|
320
|
+
__typename
|
|
321
|
+
}
|
|
322
|
+
"""
|
|
323
|
+
)
|
|
324
|
+
self.client.execute(health_query)
|
|
@@ -116,3 +116,11 @@ class Json(DB):
|
|
|
116
116
|
|
|
117
117
|
def get_plugins_data(self):
|
|
118
118
|
return self.data.get("plugins", [])
|
|
119
|
+
|
|
120
|
+
def health_check(self) -> None:
|
|
121
|
+
"""Perform a health check on the JSON database.
|
|
122
|
+
|
|
123
|
+
Raises an exception if the database is not accessible.
|
|
124
|
+
"""
|
|
125
|
+
# Try to access site_content to ensure basic structure is valid
|
|
126
|
+
self._get_site_content()
|
|
@@ -125,6 +125,14 @@ class MongoDB(DB):
|
|
|
125
125
|
return site_content.get("font", "")
|
|
126
126
|
return ""
|
|
127
127
|
|
|
128
|
+
def health_check(self) -> None:
|
|
129
|
+
"""Perform a health check on the MongoDB database.
|
|
130
|
+
|
|
131
|
+
Raises an exception if the database is not accessible.
|
|
132
|
+
"""
|
|
133
|
+
# Simple ping to check if database is accessible
|
|
134
|
+
self.client.admin.command("ping")
|
|
135
|
+
|
|
128
136
|
def _close_connection(self) -> None:
|
|
129
137
|
"""Close the MongoDB connection"""
|
|
130
138
|
if self.client:
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from concurrent.futures import ThreadPoolExecutor, TimeoutError
|
|
3
|
+
from typing import Any, Callable, Dict, List, Tuple
|
|
4
|
+
|
|
5
|
+
from flask import Blueprint, Flask, jsonify, make_response, request, session
|
|
6
|
+
from flask_babel import Babel
|
|
7
|
+
|
|
8
|
+
from platzky.config import Config
|
|
9
|
+
from platzky.models import CmsModule
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Engine(Flask):
|
|
13
|
+
def __init__(self, config: Config, db, import_name):
|
|
14
|
+
super().__init__(import_name)
|
|
15
|
+
self.config.from_mapping(config.model_dump(by_alias=True))
|
|
16
|
+
self.db = db
|
|
17
|
+
self.notifiers = []
|
|
18
|
+
self.login_methods = []
|
|
19
|
+
self.dynamic_body = ""
|
|
20
|
+
self.dynamic_head = ""
|
|
21
|
+
self.health_checks: List[Tuple[str, Callable[[], None]]] = []
|
|
22
|
+
directory = os.path.dirname(os.path.realpath(__file__))
|
|
23
|
+
locale_dir = os.path.join(directory, "locale")
|
|
24
|
+
config.translation_directories.append(locale_dir)
|
|
25
|
+
babel_translation_directories = ";".join(config.translation_directories)
|
|
26
|
+
self.babel = Babel(
|
|
27
|
+
self,
|
|
28
|
+
locale_selector=self.get_locale,
|
|
29
|
+
default_translation_directories=babel_translation_directories,
|
|
30
|
+
)
|
|
31
|
+
self._register_default_health_endpoints()
|
|
32
|
+
|
|
33
|
+
self.cms_modules: List[CmsModule] = []
|
|
34
|
+
# TODO add plugins as CMS Module - all plugins should be visible from
|
|
35
|
+
# admin page at least as configuration
|
|
36
|
+
|
|
37
|
+
def notify(self, message: str):
|
|
38
|
+
for notifier in self.notifiers:
|
|
39
|
+
notifier(message)
|
|
40
|
+
|
|
41
|
+
def add_notifier(self, notifier):
|
|
42
|
+
self.notifiers.append(notifier)
|
|
43
|
+
|
|
44
|
+
def add_cms_module(self, module: CmsModule):
|
|
45
|
+
"""Add a CMS module to the modules list."""
|
|
46
|
+
self.cms_modules.append(module)
|
|
47
|
+
|
|
48
|
+
# TODO login_method should be interface
|
|
49
|
+
def add_login_method(self, login_method):
|
|
50
|
+
self.login_methods.append(login_method)
|
|
51
|
+
|
|
52
|
+
def add_dynamic_body(self, body: str):
|
|
53
|
+
self.dynamic_body += body
|
|
54
|
+
|
|
55
|
+
def add_dynamic_head(self, body: str):
|
|
56
|
+
self.dynamic_head += body
|
|
57
|
+
|
|
58
|
+
def get_locale(self) -> str:
|
|
59
|
+
domain = request.headers.get("Host", "localhost")
|
|
60
|
+
domain_to_lang = self.config.get("DOMAIN_TO_LANG")
|
|
61
|
+
|
|
62
|
+
languages = self.config.get("LANGUAGES", {}).keys()
|
|
63
|
+
backup_lang = session.get(
|
|
64
|
+
"language",
|
|
65
|
+
request.accept_languages.best_match(languages, "en"),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if domain_to_lang:
|
|
69
|
+
lang = domain_to_lang.get(domain, backup_lang)
|
|
70
|
+
else:
|
|
71
|
+
lang = backup_lang
|
|
72
|
+
|
|
73
|
+
session["language"] = lang
|
|
74
|
+
return lang
|
|
75
|
+
|
|
76
|
+
def add_health_check(self, name: str, check_function: Callable[[], None]) -> None:
|
|
77
|
+
"""Register a health check function"""
|
|
78
|
+
if not callable(check_function):
|
|
79
|
+
raise TypeError(f"check_function must be callable, got {type(check_function)}")
|
|
80
|
+
self.health_checks.append((name, check_function))
|
|
81
|
+
|
|
82
|
+
def _register_default_health_endpoints(self):
|
|
83
|
+
"""Register default health endpoints"""
|
|
84
|
+
|
|
85
|
+
health_bp = Blueprint("health", __name__)
|
|
86
|
+
HEALTH_CHECK_TIMEOUT = 10 # seconds
|
|
87
|
+
|
|
88
|
+
@health_bp.route("/health/liveness")
|
|
89
|
+
def liveness():
|
|
90
|
+
"""Simple liveness check - is the app running?"""
|
|
91
|
+
return jsonify({"status": "alive"}), 200
|
|
92
|
+
|
|
93
|
+
@health_bp.route("/health/readiness")
|
|
94
|
+
def readiness():
|
|
95
|
+
"""Readiness check - can the app serve traffic?"""
|
|
96
|
+
health_status: Dict[str, Any] = {"status": "ready", "checks": {}}
|
|
97
|
+
status_code = 200
|
|
98
|
+
|
|
99
|
+
executor = ThreadPoolExecutor(max_workers=1)
|
|
100
|
+
try:
|
|
101
|
+
# Database health check with timeout
|
|
102
|
+
future = executor.submit(self.db.health_check)
|
|
103
|
+
try:
|
|
104
|
+
future.result(timeout=HEALTH_CHECK_TIMEOUT)
|
|
105
|
+
health_status["checks"]["database"] = "ok"
|
|
106
|
+
except TimeoutError:
|
|
107
|
+
health_status["checks"]["database"] = "failed: timeout"
|
|
108
|
+
health_status["status"] = "not_ready"
|
|
109
|
+
status_code = 503
|
|
110
|
+
except Exception as e:
|
|
111
|
+
health_status["checks"]["database"] = f"failed: {e!s}"
|
|
112
|
+
health_status["status"] = "not_ready"
|
|
113
|
+
status_code = 503
|
|
114
|
+
|
|
115
|
+
# Run application-registered health checks
|
|
116
|
+
for check_name, check_func in self.health_checks:
|
|
117
|
+
future = executor.submit(check_func)
|
|
118
|
+
try:
|
|
119
|
+
future.result(timeout=HEALTH_CHECK_TIMEOUT)
|
|
120
|
+
health_status["checks"][check_name] = "ok"
|
|
121
|
+
except TimeoutError:
|
|
122
|
+
health_status["checks"][check_name] = "failed: timeout"
|
|
123
|
+
health_status["status"] = "not_ready"
|
|
124
|
+
status_code = 503
|
|
125
|
+
except Exception as e:
|
|
126
|
+
health_status["checks"][check_name] = f"failed: {e!s}"
|
|
127
|
+
health_status["status"] = "not_ready"
|
|
128
|
+
status_code = 503
|
|
129
|
+
finally:
|
|
130
|
+
# Shutdown without waiting if any futures are still running
|
|
131
|
+
executor.shutdown(wait=False)
|
|
132
|
+
|
|
133
|
+
return make_response(jsonify(health_status), status_code)
|
|
134
|
+
|
|
135
|
+
# Simple /health alias for liveness
|
|
136
|
+
@health_bp.route("/health")
|
|
137
|
+
def health():
|
|
138
|
+
return liveness()
|
|
139
|
+
|
|
140
|
+
self.register_blueprint(health_bp)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "platzky"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3"
|
|
4
4
|
description = "Not only blog engine"
|
|
5
5
|
authors = []
|
|
6
6
|
license = "MIT"
|
|
@@ -31,8 +31,7 @@ black = "^24.8.0"
|
|
|
31
31
|
ruff = "^0.4.4"
|
|
32
32
|
pyright = "^1.1.364"
|
|
33
33
|
beautifulsoup4 = "^4.12.3"
|
|
34
|
-
python-semantic-release = "^9.
|
|
35
|
-
|
|
34
|
+
python-semantic-release = "^9.8.0"
|
|
36
35
|
|
|
37
36
|
[build-system]
|
|
38
37
|
requires = ["poetry-core"]
|
|
@@ -42,18 +41,17 @@ build-backend = "poetry.core.masonry.api"
|
|
|
42
41
|
omit = [
|
|
43
42
|
"tests/*",
|
|
44
43
|
"*/__init__.py"
|
|
45
|
-
|
|
44
|
+
]
|
|
46
45
|
|
|
47
46
|
[tool.coverage.report]
|
|
48
47
|
exclude_lines = [
|
|
49
48
|
"@abstractmethod",
|
|
50
49
|
"@abc.abstractmethod"
|
|
51
|
-
|
|
50
|
+
]
|
|
52
51
|
|
|
53
52
|
[tool.pyright]
|
|
54
53
|
pythonVersion = "3.10"
|
|
55
54
|
pythonPlatform = "All"
|
|
56
|
-
|
|
57
55
|
typeCheckingMode = "strict"
|
|
58
56
|
reportMissingImports = true
|
|
59
57
|
reportMissingTypeStubs = false
|
|
@@ -109,3 +107,9 @@ prerelease = false
|
|
|
109
107
|
[tool.semantic_release.publish]
|
|
110
108
|
dist_glob_patterns = ["dist/*"]
|
|
111
109
|
upload_to_vcs_release = true
|
|
110
|
+
|
|
111
|
+
[tool.semantic_release.remote]
|
|
112
|
+
type = "github"
|
|
113
|
+
|
|
114
|
+
[tool.semantic_release.remote.token]
|
|
115
|
+
env = "GH_TOKEN"
|
platzky-0.4.1/platzky/engine.py
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
from flask import Flask, request, session
|
|
5
|
-
from flask_babel import Babel
|
|
6
|
-
|
|
7
|
-
from platzky.config import Config
|
|
8
|
-
from platzky.models import CmsModule
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Engine(Flask):
|
|
12
|
-
def __init__(self, config: Config, db, import_name):
|
|
13
|
-
super().__init__(import_name)
|
|
14
|
-
self.config.from_mapping(config.model_dump(by_alias=True))
|
|
15
|
-
self.db = db
|
|
16
|
-
self.notifiers = []
|
|
17
|
-
self.login_methods = []
|
|
18
|
-
self.dynamic_body = ""
|
|
19
|
-
self.dynamic_head = ""
|
|
20
|
-
directory = os.path.dirname(os.path.realpath(__file__))
|
|
21
|
-
locale_dir = os.path.join(directory, "locale")
|
|
22
|
-
config.translation_directories.append(locale_dir)
|
|
23
|
-
babel_translation_directories = ";".join(config.translation_directories)
|
|
24
|
-
self.babel = Babel(
|
|
25
|
-
self,
|
|
26
|
-
locale_selector=self.get_locale,
|
|
27
|
-
default_translation_directories=babel_translation_directories,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
self.cms_modules: List[CmsModule] = []
|
|
31
|
-
# TODO add plugins as CMS Module - all plugins should be visible from
|
|
32
|
-
# admin page at least as configuration
|
|
33
|
-
|
|
34
|
-
def notify(self, message: str):
|
|
35
|
-
for notifier in self.notifiers:
|
|
36
|
-
notifier(message)
|
|
37
|
-
|
|
38
|
-
def add_notifier(self, notifier):
|
|
39
|
-
self.notifiers.append(notifier)
|
|
40
|
-
|
|
41
|
-
def add_cms_module(self, module: CmsModule):
|
|
42
|
-
"""Add a CMS module to the modules list."""
|
|
43
|
-
self.cms_modules.append(module)
|
|
44
|
-
|
|
45
|
-
# TODO login_method should be interface
|
|
46
|
-
def add_login_method(self, login_method):
|
|
47
|
-
self.login_methods.append(login_method)
|
|
48
|
-
|
|
49
|
-
def add_dynamic_body(self, body: str):
|
|
50
|
-
self.dynamic_body += body
|
|
51
|
-
|
|
52
|
-
def add_dynamic_head(self, body: str):
|
|
53
|
-
self.dynamic_head += body
|
|
54
|
-
|
|
55
|
-
def get_locale(self) -> str:
|
|
56
|
-
domain = request.headers.get("Host", "localhost")
|
|
57
|
-
domain_to_lang = self.config.get("DOMAIN_TO_LANG")
|
|
58
|
-
|
|
59
|
-
languages = self.config.get("LANGUAGES", {}).keys()
|
|
60
|
-
backup_lang = session.get(
|
|
61
|
-
"language",
|
|
62
|
-
request.accept_languages.best_match(languages, "en"),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
if domain_to_lang:
|
|
66
|
-
lang = domain_to_lang.get(domain, backup_lang)
|
|
67
|
-
else:
|
|
68
|
-
lang = backup_lang
|
|
69
|
-
|
|
70
|
-
session["language"] = lang
|
|
71
|
-
return lang
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|