platzky 1.1.0__py3-none-any.whl → 1.2.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.
platzky/admin/admin.py CHANGED
@@ -1,14 +1,24 @@
1
+ """Blueprint for admin panel functionality."""
2
+
3
+ from collections.abc import Callable
1
4
  from os.path import dirname
2
5
 
3
6
  from flask import Blueprint, render_template, session
4
7
 
8
+ from platzky.models import CmsModule
9
+
5
10
 
6
- def create_admin_blueprint(login_methods, cms_modules):
11
+ def create_admin_blueprint(
12
+ login_methods: list[Callable[[], str]], cms_modules: list[CmsModule]
13
+ ) -> Blueprint:
7
14
  """Create admin blueprint with dynamic module routes.
8
15
 
9
16
  Args:
10
17
  login_methods: Available login methods
11
18
  cms_modules: List of CMS modules to register routes for
19
+
20
+ Returns:
21
+ Configured Flask Blueprint for admin panel
12
22
  """
13
23
  # …rest of the function…
14
24
  admin = Blueprint(
@@ -21,12 +31,24 @@ def create_admin_blueprint(login_methods, cms_modules):
21
31
  for module in cms_modules:
22
32
 
23
33
  @admin.route(f"/module/{module.slug}", methods=["GET"])
24
- def module_route(module=module):
34
+ def module_route(module: CmsModule = module) -> str:
35
+ """Render a CMS module page.
25
36
 
37
+ Args:
38
+ module: CMS module object containing template and configuration
39
+
40
+ Returns:
41
+ Rendered HTML template for the module
42
+ """
26
43
  return render_template(module.template, module=module)
27
44
 
28
45
  @admin.route("/", methods=["GET"])
29
- def admin_panel_home():
46
+ def admin_panel_home() -> str:
47
+ """Display admin panel home or login page.
48
+
49
+ Returns:
50
+ Rendered login page if not authenticated, admin panel if authenticated
51
+ """
30
52
  user = session.get("user", None)
31
53
 
32
54
  if not user:
@@ -7,11 +7,11 @@ environments as it bypasses proper authentication and authorization controls.
7
7
 
8
8
  import os
9
9
  from collections.abc import Callable
10
- from typing import Any
11
10
 
12
11
  from flask import Blueprint, flash, redirect, render_template_string, session, url_for
13
12
  from flask_wtf import FlaskForm
14
13
  from markupsafe import Markup
14
+ from werkzeug.wrappers import Response
15
15
 
16
16
  ROLE_ADMIN = "admin"
17
17
  ROLE_NONADMIN = "nonadmin"
@@ -90,7 +90,7 @@ def setup_fake_login_routes(admin_blueprint: Blueprint) -> Blueprint:
90
90
  )
91
91
 
92
92
  @admin_blueprint.route("/fake-login/<role>", methods=["POST"])
93
- def handle_fake_login(role: str) -> Any:
93
+ def handle_fake_login(role: str) -> Response:
94
94
  form = FakeLoginForm()
95
95
  if form.validate_on_submit() and role in VALID_ROLES:
96
96
  if role == ROLE_ADMIN:
platzky/blog/blog.py CHANGED
@@ -1,19 +1,36 @@
1
+ """Blueprint for blog functionality including posts, pages, and comments."""
2
+
1
3
  import logging
2
4
  from collections.abc import Callable
3
5
  from os.path import dirname
4
- from typing import Any
6
+ from typing import TypeVar
5
7
 
6
8
  from flask import Blueprint, abort, make_response, render_template, request
7
9
  from markupsafe import Markup
8
10
  from werkzeug.exceptions import HTTPException
9
11
  from werkzeug.wrappers import Response
10
12
 
13
+ from platzky.db.db import DB
14
+ from platzky.models import Page, Post
15
+
11
16
  from . import comment_form
12
17
 
18
+ ContentType = TypeVar("ContentType", Post, Page)
19
+
13
20
  logger = logging.getLogger(__name__)
14
21
 
15
22
 
16
- def create_blog_blueprint(db, blog_prefix: str, locale_func):
23
+ def create_blog_blueprint(db: DB, blog_prefix: str, locale_func: Callable[[], str]) -> Blueprint:
24
+ """Create and configure the blog blueprint with all routes and handlers.
25
+
26
+ Args:
27
+ db: Database instance for accessing blog content
28
+ blog_prefix: URL prefix for blog routes
29
+ locale_func: Function that returns the current locale/language code
30
+
31
+ Returns:
32
+ Configured Flask Blueprint for blog functionality
33
+ """
17
34
  url_prefix = blog_prefix
18
35
  blog = Blueprint(
19
36
  "blog",
@@ -23,7 +40,15 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
23
40
  )
24
41
 
25
42
  @blog.app_template_filter()
26
- def markdown(text):
43
+ def markdown(text: str) -> Markup:
44
+ """Template filter to render markdown text as safe HTML.
45
+
46
+ Args:
47
+ text: Markdown text to be rendered
48
+
49
+ Returns:
50
+ Markup object containing safe HTML
51
+ """
27
52
  return Markup(text)
28
53
 
29
54
  @blog.errorhandler(404)
@@ -40,6 +65,11 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
40
65
 
41
66
  @blog.route("/", methods=["GET"])
42
67
  def all_posts() -> str:
68
+ """Display all blog posts for the current language.
69
+
70
+ Returns:
71
+ Rendered HTML template with all blog posts
72
+ """
43
73
  lang = locale_func()
44
74
  posts = db.get_all_posts(lang)
45
75
  if not posts:
@@ -49,6 +79,11 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
49
79
 
50
80
  @blog.route("/feed", methods=["GET"])
51
81
  def get_feed() -> Response:
82
+ """Generate RSS/Atom feed for blog posts.
83
+
84
+ Returns:
85
+ XML response containing the RSS/Atom feed
86
+ """
52
87
  lang = locale_func()
53
88
  response = make_response(render_template("feed.xml", posts=db.get_all_posts(lang)))
54
89
  response.headers["Content-Type"] = "application/xml"
@@ -56,6 +91,14 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
56
91
 
57
92
  @blog.route("/<post_slug>", methods=["POST"])
58
93
  def post_comment(post_slug: str) -> str:
94
+ """Handle comment submission for a blog post.
95
+
96
+ Args:
97
+ post_slug: URL slug of the blog post
98
+
99
+ Returns:
100
+ Rendered HTML template of the blog post with new comment
101
+ """
59
102
  comment = request.form.to_dict()
60
103
  db.add_comment(
61
104
  post_slug=post_slug,
@@ -65,9 +108,9 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
65
108
  return get_post(post_slug=post_slug)
66
109
 
67
110
  def _get_content_or_404(
68
- getter_func: Callable[[str], Any],
111
+ getter_func: Callable[[str], ContentType],
69
112
  slug: str,
70
- ) -> Any:
113
+ ) -> ContentType:
71
114
  """Helper to fetch content from database or abort with 404.
72
115
 
73
116
  Args:
@@ -88,6 +131,14 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
88
131
 
89
132
  @blog.route("/<post_slug>", methods=["GET"])
90
133
  def get_post(post_slug: str) -> str:
134
+ """Display a single blog post with comments.
135
+
136
+ Args:
137
+ post_slug: URL slug of the blog post
138
+
139
+ Returns:
140
+ Rendered HTML template of the blog post
141
+ """
91
142
  post = _get_content_or_404(db.get_post, post_slug)
92
143
  return render_template(
93
144
  "post.html",
@@ -99,12 +150,28 @@ def create_blog_blueprint(db, blog_prefix: str, locale_func):
99
150
 
100
151
  @blog.route("/page/<path:page_slug>", methods=["GET"])
101
152
  def get_page(page_slug: str) -> str:
153
+ """Display a static page.
154
+
155
+ Args:
156
+ page_slug: URL slug of the page
157
+
158
+ Returns:
159
+ Rendered HTML template of the page
160
+ """
102
161
  page = _get_content_or_404(db.get_page, page_slug)
103
162
  cover_image_url = page.coverImage.url if page.coverImage.url else None
104
163
  return render_template("page.html", page=page, cover_image=cover_image_url)
105
164
 
106
165
  @blog.route("/tag/<path:tag>", methods=["GET"])
107
166
  def get_posts_from_tag(tag: str) -> str:
167
+ """Display all blog posts with a specific tag.
168
+
169
+ Args:
170
+ tag: Tag name to filter posts by
171
+
172
+ Returns:
173
+ Rendered HTML template with filtered blog posts
174
+ """
108
175
  lang = locale_func()
109
176
  posts = db.get_posts_by_tag(tag, lang)
110
177
  return render_template("blog.html", posts=posts, subtitle=f" - tag: {tag}")
@@ -1,3 +1,5 @@
1
+ """Form for blog post comments."""
2
+
1
3
  from flask_babel import lazy_gettext
2
4
  from flask_wtf import FlaskForm
3
5
  from wtforms import StringField, SubmitField
@@ -6,6 +8,14 @@ from wtforms.widgets import TextArea
6
8
 
7
9
 
8
10
  class CommentForm(FlaskForm):
11
+ """Form for submitting comments on blog posts.
12
+
13
+ Attributes:
14
+ author_name: Required text field for the commenter's name.
15
+ comment: Required text area for the comment content.
16
+ submit: Submit button to post the comment.
17
+ """
18
+
9
19
  author_name = StringField(str(lazy_gettext("Name")), validators=[DataRequired()])
10
20
  comment = StringField(
11
21
  str(lazy_gettext("Type comment here")),
platzky/config.py CHANGED
@@ -165,7 +165,7 @@ class Config(StrictBaseModel):
165
165
  @classmethod
166
166
  def model_validate(
167
167
  cls,
168
- obj: t.Any,
168
+ obj: dict[str, t.Any],
169
169
  *,
170
170
  strict: bool | None = None,
171
171
  from_attributes: bool | None = None,
platzky/db/db.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Abstract base classes for database implementations."""
2
+
1
3
  from abc import ABC, abstractmethod
2
4
  from collections.abc import Callable
3
5
  from functools import partial
@@ -9,6 +11,8 @@ from platzky.models import MenuItem, Page, Post
9
11
 
10
12
 
11
13
  class DB(ABC):
14
+ """Abstract base class for all database implementations."""
15
+
12
16
  db_name: str = "DB"
13
17
  module_name: str = "db"
14
18
  config_type: type
@@ -46,55 +50,99 @@ class DB(ABC):
46
50
  raise ValueError(f"Failed to extend DB with function {function_name}: {e}")
47
51
 
48
52
  @abstractmethod
49
- def get_app_description(self, lang) -> str:
53
+ def get_app_description(self, lang: str) -> str:
54
+ """Retrieve the application description for a specific language.
55
+
56
+ Args:
57
+ lang: Language code (e.g., 'en', 'pl')
58
+ """
50
59
  pass
51
60
 
52
61
  @abstractmethod
53
- def get_all_posts(self, lang) -> list[Post]:
62
+ def get_all_posts(self, lang: str) -> list[Post]:
63
+ """Retrieve all posts for a specific language.
64
+
65
+ Args:
66
+ lang: Language code (e.g., 'en', 'pl')
67
+ """
54
68
  pass
55
69
 
56
70
  @abstractmethod
57
- def get_menu_items_in_lang(self, lang) -> list[MenuItem]:
71
+ def get_menu_items_in_lang(self, lang: str) -> list[MenuItem]:
72
+ """Retrieve menu items for a specific language.
73
+
74
+ Args:
75
+ lang: Language code (e.g., 'en', 'pl')
76
+ """
58
77
  pass
59
78
 
60
79
  @abstractmethod
61
- def get_post(self, slug) -> Post:
80
+ def get_post(self, slug: str) -> Post:
81
+ """Retrieve a single post by its slug.
82
+
83
+ Args:
84
+ slug: URL-friendly identifier for the post
85
+ """
62
86
  pass
63
87
 
64
88
  @abstractmethod
65
- def get_page(self, slug) -> Page:
89
+ def get_page(self, slug: str) -> Page:
90
+ """Retrieve a page by its slug.
91
+
92
+ Args:
93
+ slug: URL-friendly identifier for the page
94
+ """
66
95
  pass
67
96
 
68
97
  @abstractmethod
69
- def get_posts_by_tag(self, tag, lang) -> Any:
98
+ def get_posts_by_tag(self, tag: str, lang: str) -> list[Post]:
99
+ """Retrieve posts filtered by tag and language.
100
+
101
+ Args:
102
+ tag: Tag name to filter by
103
+ lang: Language code (e.g., 'en', 'pl')
104
+ """
70
105
  pass
71
106
 
72
107
  @abstractmethod
73
- def add_comment(self, author_name, comment, post_slug) -> None:
108
+ def add_comment(self, author_name: str, comment: str, post_slug: str) -> None:
109
+ """Add a new comment to a post.
110
+
111
+ Args:
112
+ author_name: Name of the comment author
113
+ comment: Comment text content
114
+ post_slug: URL-friendly identifier of the post
115
+ """
74
116
  pass
75
117
 
76
118
  @abstractmethod
77
- def get_logo_url(self) -> str: # TODO provide alternative text along with the URL of logo
119
+ def get_logo_url(self) -> str: # TODO: Provide alternative text along with the URL of logo
120
+ """Retrieve the URL of the application logo."""
78
121
  pass
79
122
 
80
123
  @abstractmethod
81
124
  def get_favicon_url(self) -> str:
125
+ """Retrieve the URL of the application favicon."""
82
126
  pass
83
127
 
84
128
  @abstractmethod
85
129
  def get_primary_color(self) -> str:
130
+ """Retrieve the primary color for the application theme."""
86
131
  pass
87
132
 
88
133
  @abstractmethod
89
134
  def get_secondary_color(self) -> str:
135
+ """Retrieve the secondary color for the application theme."""
90
136
  pass
91
137
 
92
138
  @abstractmethod
93
- def get_plugins_data(self) -> list[Any]:
139
+ def get_plugins_data(self) -> list[dict[str, Any]]:
140
+ """Retrieve configuration data for all plugins."""
94
141
  pass
95
142
 
96
143
  @abstractmethod
97
144
  def get_font(self) -> str:
145
+ """Get the font configuration for the application."""
98
146
  pass
99
147
 
100
148
  @abstractmethod
@@ -108,4 +156,6 @@ class DB(ABC):
108
156
 
109
157
 
110
158
  class DBConfig(BaseModel):
159
+ """Base configuration class for database connections."""
160
+
111
161
  type: str = Field(alias="TYPE")
platzky/db/db_loader.py CHANGED
@@ -1,16 +1,19 @@
1
1
  import importlib.util
2
2
  import os
3
3
  import sys
4
+ import types
4
5
  from os.path import abspath, dirname
5
6
 
7
+ from platzky.db.db import DB, DBConfig
6
8
 
7
- def get_db(db_config):
9
+
10
+ def get_db(db_config: DBConfig) -> DB:
8
11
  db_name = db_config.type
9
12
  db = get_db_module(db_name)
10
13
  return db.db_from_config(db_config)
11
14
 
12
15
 
13
- def get_db_module(db_type):
16
+ def get_db_module(db_type: str) -> types.ModuleType:
14
17
  """
15
18
  Load db module from db_type
16
19
  This function is used to load db module dynamically as it is specified in config file.
@@ -1,4 +1,7 @@
1
+ """GitHub-based JSON database implementation."""
2
+
1
3
  import json
4
+ from typing import Any
2
5
 
3
6
  import requests
4
7
  from github import Github
@@ -8,24 +11,47 @@ from platzky.db.db import DBConfig
8
11
  from platzky.db.json_db import Json as JsonDB
9
12
 
10
13
 
11
- def db_config_type():
14
+ def db_config_type() -> type["GithubJsonDbConfig"]:
15
+ """Return the configuration class for GitHub JSON database.
16
+
17
+ Returns:
18
+ GithubJsonDbConfig class
19
+ """
12
20
  return GithubJsonDbConfig
13
21
 
14
22
 
15
23
  class GithubJsonDbConfig(DBConfig):
24
+ """Configuration for GitHub JSON database connection."""
25
+
16
26
  github_token: str = Field(alias="GITHUB_TOKEN")
17
27
  repo_name: str = Field(alias="REPO_NAME")
18
28
  path_to_file: str = Field(alias="PATH_TO_FILE")
19
29
  branch_name: str = Field(alias="BRANCH_NAME", default="main")
20
30
 
21
31
 
22
- def db_from_config(config: GithubJsonDbConfig):
32
+ def db_from_config(config: GithubJsonDbConfig) -> "GithubJsonDb":
33
+ """Create a GitHub JSON database instance from configuration.
34
+
35
+ Args:
36
+ config: GitHub JSON database configuration
37
+
38
+ Returns:
39
+ Configured GitHub JSON database instance
40
+ """
23
41
  return GithubJsonDb(
24
42
  config.github_token, config.repo_name, config.branch_name, config.path_to_file
25
43
  )
26
44
 
27
45
 
28
- def get_db(config):
46
+ def get_db(config: dict[str, Any]) -> "GithubJsonDb":
47
+ """Get a GitHub JSON database instance from raw configuration.
48
+
49
+ Args:
50
+ config: Raw configuration dictionary
51
+
52
+ Returns:
53
+ Configured GitHub JSON database instance
54
+ """
29
55
  github_json_db_config = GithubJsonDbConfig.model_validate(config)
30
56
  return GithubJsonDb(
31
57
  github_json_db_config.github_token,
@@ -36,7 +62,19 @@ def get_db(config):
36
62
 
37
63
 
38
64
  class GithubJsonDb(JsonDB):
39
- def __init__(self, github_token: str, repo_name: str, branch_name: str, path_to_file: str):
65
+ """JSON database stored in a GitHub repository."""
66
+
67
+ def __init__(
68
+ self, github_token: str, repo_name: str, branch_name: str, path_to_file: str
69
+ ) -> None:
70
+ """Initialize GitHub JSON database connection.
71
+
72
+ Args:
73
+ github_token: GitHub personal access token
74
+ repo_name: Full repository name (e.g., 'owner/repo')
75
+ branch_name: Branch name to read from
76
+ path_to_file: Path to the JSON file within the repository
77
+ """
40
78
  self.branch_name = branch_name
41
79
  self.repo = Github(github_token).get_repo(repo_name)
42
80
  self.file_path = path_to_file
@@ -1,4 +1,7 @@
1
+ """Google Cloud Storage-based JSON database implementation."""
2
+
1
3
  import json
4
+ from typing import TYPE_CHECKING, Any
2
5
 
3
6
  from google.cloud.storage import Client
4
7
  from pydantic import Field
@@ -6,38 +9,91 @@ from pydantic import Field
6
9
  from platzky.db.db import DBConfig
7
10
  from platzky.db.json_db import Json
8
11
 
12
+ if TYPE_CHECKING:
13
+ from google.cloud.storage import Blob
14
+
9
15
 
10
- def db_config_type():
16
+ def db_config_type() -> type["GoogleJsonDbConfig"]:
17
+ """Return the configuration class for Google Cloud Storage JSON database.
18
+
19
+ Returns:
20
+ GoogleJsonDbConfig class
21
+ """
11
22
  return GoogleJsonDbConfig
12
23
 
13
24
 
14
25
  class GoogleJsonDbConfig(DBConfig):
26
+ """Configuration for Google Cloud Storage JSON database connection."""
27
+
15
28
  bucket_name: str = Field(alias="BUCKET_NAME")
16
29
  source_blob_name: str = Field(alias="SOURCE_BLOB_NAME")
17
30
 
18
31
 
19
- def db_from_config(config: GoogleJsonDbConfig):
32
+ def db_from_config(config: GoogleJsonDbConfig) -> "GoogleJsonDb":
33
+ """Create a Google Cloud Storage JSON database instance from configuration.
34
+
35
+ Args:
36
+ config: Google Cloud Storage JSON database configuration
37
+
38
+ Returns:
39
+ Configured Google Cloud Storage JSON database instance
40
+ """
20
41
  return GoogleJsonDb(config.bucket_name, config.source_blob_name)
21
42
 
22
43
 
23
- def get_db(config):
44
+ def get_db(config: dict[str, Any]) -> "GoogleJsonDb":
45
+ """Get a Google Cloud Storage JSON database instance from raw configuration.
46
+
47
+ Args:
48
+ config: Raw configuration dictionary
49
+
50
+ Returns:
51
+ Configured Google Cloud Storage JSON database instance
52
+ """
24
53
  google_json_db_config = GoogleJsonDbConfig.model_validate(config)
25
54
  return GoogleJsonDb(google_json_db_config.bucket_name, google_json_db_config.source_blob_name)
26
55
 
27
56
 
28
- def get_blob(bucket_name, source_blob_name):
57
+ def get_blob(bucket_name: str, source_blob_name: str) -> "Blob":
58
+ """Retrieve a blob from Google Cloud Storage.
59
+
60
+ Args:
61
+ bucket_name: Name of the GCS bucket
62
+ source_blob_name: Name of the blob/file in the bucket
63
+
64
+ Returns:
65
+ GCS Blob object
66
+ """
29
67
  storage_client = Client()
30
68
  bucket = storage_client.bucket(bucket_name)
31
69
  return bucket.blob(source_blob_name)
32
70
 
33
71
 
34
- def get_data(blob):
35
- raw_data = blob.download_as_text(client=None)
72
+ def get_data(blob: "Blob") -> dict[str, Any]:
73
+ """Download and parse JSON data from a blob.
74
+
75
+ Args:
76
+ blob: GCS Blob object to download from
77
+
78
+ Returns:
79
+ Parsed JSON data as dictionary
80
+ """
81
+ raw_data = (
82
+ blob.download_as_text()
83
+ ) # pyright: ignore[reportCallIssue] - Incomplete type stubs for google.cloud.storage Blob
36
84
  return json.loads(raw_data)
37
85
 
38
86
 
39
87
  class GoogleJsonDb(Json):
40
- def __init__(self, bucket_name, source_blob_name):
88
+ """JSON database stored in Google Cloud Storage."""
89
+
90
+ def __init__(self, bucket_name: str, source_blob_name: str) -> None:
91
+ """Initialize Google Cloud Storage JSON database connection.
92
+
93
+ Args:
94
+ bucket_name: Name of the GCS bucket
95
+ source_blob_name: Name of the blob/file in the bucket
96
+ """
41
97
  self.bucket_name = bucket_name
42
98
  self.source_blob_name = source_blob_name
43
99