Skip to content

Commit

Permalink
* refactor SomePerson class into a "framework" class
Browse files Browse the repository at this point in the history
* update seo framework plugin to account for use of pelican.plugins.sitemap
  • Loading branch information
aaronmarkey committed Feb 29, 2024
1 parent 86b3742 commit 7c0f378
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 184 deletions.
16 changes: 3 additions & 13 deletions pelicanconf.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from someperson import (
Author,
Configuration,
Link,
Palette,
SomePersonConfig,
Theme,
Twitter,
)
from someperson.plugins.markdown import MarkdownPlugin
from someperson.plugins.seo import SeoPlugin
from someperson.plugins.theme import ThemePlugin
from someperson.plugins import MarkdownPlugin, SeoPlugin, ThemePlugin

################################
# Theme - Start
################################
SOME_PERSON = SomePersonConfig(
SOME_PERSON = Configuration(
author=Author(
first_names=[
"aaron",
Expand Down Expand Up @@ -124,14 +122,6 @@
"extra/favicon.ico": {"path": "favicon.ico"},
}
LOCALE = ("en_US",)
MARKDOWN = {
"extension_configs": {
"markdown.extensions.codehilite": {"css_class": "highlight"},
"markdown.extensions.extra": {},
"markdown.extensions.meta": {},
},
"output_format": "html5",
}
OUTPUT_PATH = "output/"
OUTPUT_SOURCES = False
SITENAME = "some person"
Expand Down
162 changes: 22 additions & 140 deletions someperson/__init__.py
Original file line number Diff line number Diff line change
@@ -1,143 +1,25 @@
from functools import cached_property
from secrets import choice

from pelican import Pelican, signals
from pydantic import BaseModel

from someperson.plugins.base import Plugin, PluginHandler
from someperson.plugins.markdown import MarkdownPlugin
from someperson.plugins.seo import SeoPlugin
from someperson.plugins.theme import ThemePlugin


class SocialAccount(BaseModel):
username: str
link_format: str

@property
def link(self) -> str:
return self.link_format.format(username=self.username)


class Twitter(SocialAccount):
link_format: str = "https://twitter.com/{username}"


class Link(BaseModel):
title: str
href: str


class Author(BaseModel):
first_names: list[str]
last_names: list[str]
twitter: Twitter

@property
def name_parts(self) -> tuple[str, str]:
return self.first_names[0] or "", self.last_names[0] or ""

@property
def name(self) -> str:
first, last = self.name_parts
return f"{first} {last}".strip()

@property
def rand_name_parts(self) -> tuple[str, str]:
return choice(self.first_names) or "", choice(self.last_names[0]) or ""

@property
def names(self) -> dict[str, list[str]]:
return {"first": self.first_names, "last": self.last_names}


class Palette(BaseModel):
id: str
icon: str
name: str
emojis: list[str] = []

@property
def serialized(self) -> dict[str, str]:
return self.model_dump()


class Theme(BaseModel):
palettes: list[Palette]
default_palette_id: str
hash: str = ""

@cached_property
def default_palette(self) -> Palette:
palettes = {p.id: p for p in self.palettes}
if default := palettes.get(self.default_palette_id):
return default
err = f"No palette for default ID '{self.default_palette_id}'."
raise ValueError(err)


class SomePersonConfig(BaseModel):
author: Author
description: str
theme: Theme
menu: list[Link] = []
plugins: list[Plugin]


class SomePerson:
_supported_plugins: tuple[type[Plugin], ...] = (ThemePlugin, MarkdownPlugin, SeoPlugin)
_supported_signals: tuple[str, ...] = ("initialized", "content_written", "generator_init")

def __init__(
self,
*,
pelican_signal=signals,
settings_key: str = "SOME_PERSON",
) -> None:
self.settings_key = settings_key
self.signals = pelican_signal

self.theme_config: SomePersonConfig | None = None
self._pelican_settings = None

def receiver(signal_name: str, handler: PluginHandler):
def _reciever(*args, **kwargs):
if (method := getattr(handler, f"sig_{signal_name}", None)) and handler.config.enabled:
method(*args, **kwargs)

return _reciever

self._handlers: dict[str, PluginHandler] = {}
for plugin_cls in self._supported_plugins:
plugin = plugin_cls()
hcls = plugin_cls.handler
self._handlers[plugin_cls.__name__] = hcls(plugin)

for signal_name in self._supported_signals:
setattr(
self,
f"_r_{hcls.__name__}_{signal_name}",
receiver(signal_name, self._handlers[plugin_cls.__name__]),
)

def _r_initialized(self, app: Pelican) -> bool:
self.theme_config = app.settings.get(self.settings_key)
self._pelican_settings = app.settings
for plugin in self.theme_config.plugins:
self._handlers[type(plugin).__name__].connect(self._pelican_settings, self.theme_config, plugin)

def register(self) -> None:
self.signals.initialized.connect(self._r_initialized)
for handler in self._handlers.values():
for signal_name in self._supported_signals:
if (method := getattr(self, f"_r_{type(handler).__name__}_{signal_name}", None)) and (
signal := getattr(self.signals, signal_name, None)
):
signal.connect(method)


some_person = SomePerson()
from pelican.plugins.signals import content_written, finalized, generator_init, initialized

from someperson.configuration import ( # noqa: F401
Author,
Configuration,
Link,
Palette,
Theme,
Twitter,
)
from someperson.framework import Framework

framework = Framework(
supported_signals=(
initialized,
content_written,
generator_init,
finalized,
),
settings_key="SOME_PERSON",
)


def register() -> None:
some_person.register()
framework.configure()
85 changes: 85 additions & 0 deletions someperson/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from functools import cached_property
from secrets import choice

from pydantic import BaseModel

from someperson.plugins.base import Plugin


class StaticManifest(BaseModel):
hash: str
youtube_use_list: bool


class SocialAccount(BaseModel):
username: str
link_format: str

@property
def link(self) -> str:
return self.link_format.format(username=self.username)


class Twitter(SocialAccount):
link_format: str = "https://twitter.com/{username}"


class Link(BaseModel):
title: str
href: str


class Author(BaseModel):
first_names: list[str]
last_names: list[str]
twitter: Twitter

@property
def name_parts(self) -> tuple[str, str]:
return self.first_names[0] or "", self.last_names[0] or ""

@property
def name(self) -> str:
first, last = self.name_parts
return f"{first} {last}".strip()

@property
def rand_name_parts(self) -> tuple[str, str]:
return choice(self.first_names) or "", choice(self.last_names[0]) or ""

@property
def names(self) -> dict[str, list[str]]:
return {"first": self.first_names, "last": self.last_names}


class Palette(BaseModel):
id: str
icon: str
name: str
emojis: list[str] = []

@property
def serialized(self) -> dict[str, str]:
return self.model_dump()


class Theme(BaseModel):
palettes: list[Palette]
default_palette_id: str
hash: str = ""

@cached_property
def default_palette(self) -> Palette:
palettes = {p.id: p for p in self.palettes}
if default := palettes.get(self.default_palette_id):
return default
err = f"No palette for default ID '{self.default_palette_id}'."
raise ValueError(err)


class Configuration(BaseModel):
author: Author
description: str
theme: Theme
menu: list[Link] = []
plugins: list[Plugin]
51 changes: 51 additions & 0 deletions someperson/framework.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
from collections.abc import Sequence
from typing import TYPE_CHECKING

from blinker import NamedSignal
from pelican import Pelican

if TYPE_CHECKING:
from someperson.configuration import Configuration

from someperson.plugins.base import PluginHandler
from someperson.utils import Settings, get_signal_name


class Framework:
@staticmethod
def receiver_name(signal: NamedSignal) -> str:
name = get_signal_name(signal)
return f"_receiver_for_{name}"

def __init__(self, *, supported_signals: Sequence[NamedSignal], settings_key: str) -> None:
self._settings_key = settings_key
self._supported_signals: dict[str, NamedSignal] = {
get_signal_name(signal): signal for signal in supported_signals
}
self.framework_config: Configuration | None = None
self.pelican_config: Settings | None = None
self.plugins: list[PluginHandler] = []

for signal in self._supported_signals.values():
setattr(self, Framework.receiver_name(signal), self._receiver_factory(signal))

def configure(self) -> None:
self._supported_signals["initialized"].connect(self._signal_initialized)
for signal in self._supported_signals.values():
if method := getattr(self, Framework.receiver_name(signal), None):
signal.connect(method)

def _signal_initialized(self, app: Pelican) -> None:
self.pelican_config = app.settings
self.framework_config = app.settings.get(self._settings_key)
for plugin in self.framework_config.plugins:
handler = plugin.handler(plugin, self.pelican_config, self.framework_config)
self.plugins.append(handler)

def _receiver_factory(self, signal: NamedSignal):
def receiver(*args, **kwargs) -> None:
for handler in self.plugins:
if (method := getattr(handler, PluginHandler.receiver_name(signal), None)) and handler.config.enabled:
method(*args, **kwargs)

return receiver
3 changes: 3 additions & 0 deletions someperson/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from someperson.plugins.markdown import MarkdownPlugin # noqa: F401
from someperson.plugins.seo import SeoPlugin # noqa: F401
from someperson.plugins.theme import ThemePlugin # noqa: F401
23 changes: 13 additions & 10 deletions someperson/plugins/base.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
from typing import TYPE_CHECKING, Any, ClassVar

from blinker import NamedSignal
from pydantic import BaseModel

from someperson.utils import Settings, get_signal_name

if TYPE_CHECKING:
from someperson import SomePersonConfig
from someperson.configuration import Configuration


class PluginHandler:
def __init__(self, config: "Plugin") -> None:
self._pelican_settings = None
self.theme_config: "SomePersonConfig" | None = None
self.config: "Plugin" = config
def __init__(self, config: "Plugin", pelican_config: Settings, framework_config: "Configuration") -> None:
self.pelican_config = pelican_config
self.framework_config = framework_config
self.config = config

def get_pelican_setting(self, name: str) -> Any | None:
return self._pelican_settings.get(name, None)
return self.pelican_config.get(name, None)

def connect(self, pelican_settings, theme_config: "SomePersonConfig", config: "Plugin") -> None:
self.config = type(self.config)(**{**self.config.model_dump(), **config.model_dump()})
self.theme_config = theme_config
self._pelican_settings = pelican_settings
@staticmethod
def receiver_name(signal: NamedSignal) -> str:
name = get_signal_name(signal)
return f"signal_{name}"


class Plugin(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion someperson/plugins/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@


class MarkdownPluginHandler(PluginHandler):
def sig_initialized(self, app: Pelican) -> bool:
def signal_initialized(self, app: Pelican) -> None:
if self.config.youtube:
app.settings["MARKDOWN"]["extension_configs"]["someperson.markdown.youtube"] = {
key.split("_", 1)[1]: value
Expand Down
Loading

0 comments on commit 7c0f378

Please sign in to comment.