diff --git a/blag/__init__.py b/blag/__init__.py index 6527216..318e4f3 100644 --- a/blag/__init__.py +++ b/blag/__init__.py @@ -1 +1 @@ -from blag.version import __VERSION__ # noqa +from blag.version import __VERSION__ as __VERSION__ # noqa diff --git a/blag/blag.py b/blag/blag.py index f1c1b1a..8a9ce81 100644 --- a/blag/blag.py +++ b/blag/blag.py @@ -6,6 +6,7 @@ # remove when we don't support py38 anymore from __future__ import annotations +from typing import Any import argparse import os import shutil @@ -34,7 +35,7 @@ logging.basicConfig( ) -def main(arguments: list[str] = None) -> None: +def main(arguments: list[str] | None = None) -> None: """Main entrypoint for the CLI. This method parses the CLI arguments and executes the respective @@ -54,7 +55,7 @@ def main(arguments: list[str] = None) -> None: args.func(args) -def parse_args(args: list[str] = None) -> argparse.Namespace: +def parse_args(args: list[str] | None = None) -> argparse.Namespace: """Parse command line arguments. Parameters @@ -175,8 +176,8 @@ def get_config(configfile: str) -> configparser.SectionProxy: def environment_factory( - template_dir: str = None, - globals_: dict = None, + template_dir: str | None = None, + globals_: dict[str, object] | None = None, ) -> Environment: """Environment factory. @@ -188,7 +189,7 @@ def environment_factory( Parameters ---------- template_dir : str - globals_ : dict + globals_ : dict[str, object] Returns ------- @@ -278,7 +279,7 @@ def process_markdown( output_dir: str, page_template: Template, article_template: Template, -) -> tuple[list[tuple[str, dict]], list[tuple[str, dict]]]: +) -> tuple[list[tuple[str, dict[str, Any]]], list[tuple[str, dict[str, Any]]]]: """Process markdown files. This method processes the convertibles, converts them to html and @@ -298,7 +299,7 @@ def process_markdown( Returns ------- - articles, pages : list[tuple[str, dict]] + articles, pages : list[tuple[str, dict[str, Any]]] """ logger.info("Converting Markdown files...") @@ -334,7 +335,7 @@ def process_markdown( def generate_feed( - articles: list[tuple[str, dict]], + articles: list[tuple[str, dict[str, Any]]], output_dir: str, base_url: str, blog_title: str, @@ -345,7 +346,7 @@ def generate_feed( Parameters ---------- - articles : list[tuple[str, dict]] + articles : list[tuple[str, dict[str, Any]]] list of relative output path and article dictionary output_dir : str where the feed is stored @@ -386,7 +387,7 @@ def generate_feed( def generate_archive( - articles: list[tuple[str, dict]], + articles: list[tuple[str, dict[str, Any]]], template: Template, output_dir: str, ) -> None: @@ -394,7 +395,7 @@ def generate_archive( Parameters ---------- - articles : list[tuple[str, dict]] + articles : list[tuple[str, dict[str, Any]]] List of articles. Each article has the destination path and a dictionary with the content. template : jinja2.Template instance @@ -413,7 +414,7 @@ def generate_archive( def generate_tags( - articles: list[tuple[str, dict]], + articles: list[tuple[str, dict[str, Any]]], tags_template: Template, tag_template: Template, output_dir: str, @@ -422,7 +423,7 @@ def generate_tags( Parameters ---------- - articles : list[tuple[str, dict]] + articles : list[tuple[str, dict[str, Any]]] List of articles. Each article has the destination path and a dictionary with the content. tags_template, tag_template : jinja2.Template instance @@ -431,11 +432,10 @@ def generate_tags( """ logger.info("Generating Tag-pages.") os.makedirs(f'{output_dir}/tags', exist_ok=True) - # get tags number of occurrences - all_tags: dict = {} + all_tags: dict[str, int] = {} for _, context in articles: - tags = context.get('tags', []) + tags: list[str] = context.get('tags', []) for tag in tags: all_tags[tag] = all_tags.get(tag, 0) + 1 # sort by occurrence @@ -448,17 +448,17 @@ def generate_tags( fh.write(result) # get tags and archive per tag - all_tags = {} + all_tags2: dict[str, list[dict[str, Any]]] = {} for dst, context in articles: tags = context.get('tags', []) for tag in tags: - archive = all_tags.get(tag, []) + archive: list[dict[str, Any]] = all_tags2.get(tag, []) entry = context.copy() entry['dst'] = dst archive.append(entry) - all_tags[tag] = archive + all_tags2[tag] = archive - for tag, archive in all_tags.items(): + for tag, archive in all_tags2.items(): result = tag_template.render(dict(archive=archive, tag=tag)) with open(f'{output_dir}/tags/{tag}.html', 'w') as fh: fh.write(result) diff --git a/blag/markdown.py b/blag/markdown.py index b71df4b..efe0926 100644 --- a/blag/markdown.py +++ b/blag/markdown.py @@ -41,7 +41,10 @@ def markdown_factory() -> Markdown: return md -def convert_markdown(md: Markdown, markdown: str) -> tuple[str, dict]: +def convert_markdown( + md: Markdown, + markdown: str, +) -> tuple[str, dict[str, str]]: """Convert markdown into html and extract meta data. Some meta data is treated special: @@ -56,13 +59,13 @@ def convert_markdown(md: Markdown, markdown: str) -> tuple[str, dict]: Returns ------- - str, dict : + str, dict[str, str] : html and metadata """ md.reset() content = md.convert(markdown) - meta = md.Meta + meta = md.Meta # type: ignore # markdowns metadata consists as list of strings -- one item per # line. let's convert into single strings. @@ -90,7 +93,7 @@ class MarkdownLinkTreeprocessor(Treeprocessor): """ - def run(self, root: Element): + def run(self, root: Element) -> Element: for element in root.iter(): if element.tag == 'a': url = element.get('href') @@ -102,7 +105,7 @@ class MarkdownLinkTreeprocessor(Treeprocessor): element.set('href', converted) return root - def convert(self, url: str): + def convert(self, url: str) -> str: scheme, netloc, path, query, fragment = urlsplit(url) logger.debug( f'{url}: {scheme=} {netloc=} {path=} {query=} {fragment=}' @@ -120,7 +123,7 @@ class MarkdownLinkExtension(Extension): """markdown.extension that converts relative .md- to .html-links. """ - def extendMarkdown(self, md: Markdown): + def extendMarkdown(self, md: Markdown) -> None: md.treeprocessors.register( MarkdownLinkTreeprocessor(md), 'mdlink', 0, ) diff --git a/blag/quickstart.py b/blag/quickstart.py index dc82380..cbdcaac 100644 --- a/blag/quickstart.py +++ b/blag/quickstart.py @@ -32,7 +32,7 @@ def get_input(question: str, default: str) -> str: return reply -def quickstart(args: argparse.Namespace) -> None: +def quickstart(args: argparse.Namespace | None) -> None: """Quickstart. This method asks the user some questions and generates a diff --git a/setup.cfg b/setup.cfg index 71030af..94d778c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,6 +10,7 @@ exclude = venv,build,docs [mypy] files = blag,tests +strict = True [mypy-feedgenerator.*] ignore_missing_imports = True diff --git a/tests/conftest.py b/tests/conftest.py index 221078c..f774841 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,16 @@ +from argparse import Namespace +from typing import Iterator, Callable from tempfile import TemporaryDirectory import os import pytest +from jinja2 import Environment, Template from blag import blag @pytest.fixture -def environment(): +def environment() -> Iterator[Environment]: site = { 'base_url': 'site base_url', 'title': 'site title', @@ -19,32 +22,32 @@ def environment(): @pytest.fixture -def page_template(environment): +def page_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('page.html') @pytest.fixture -def article_template(environment): +def article_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('article.html') @pytest.fixture -def archive_template(environment): +def archive_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('archive.html') @pytest.fixture -def tags_template(environment): +def tags_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('tags.html') @pytest.fixture -def tag_template(environment): +def tag_template(environment: Environment) -> Iterator[Template]: yield environment.get_template('tag.html') @pytest.fixture -def cleandir(): +def cleandir() -> Iterator[str]: """Create a temporary workind directory and cwd. """ @@ -70,14 +73,9 @@ author = a. u. thor @pytest.fixture -def args(cleandir): +def args(cleandir: Callable[[], Iterator[str]]) -> Iterator[Namespace]: - class NameSpace: - def __init__(self, **kwargs): - for name in kwargs: - setattr(self, name, kwargs[name]) - - args = NameSpace( + args = Namespace( input_dir='content', output_dir='build', static_dir='static', diff --git a/tests/test_blag.py b/tests/test_blag.py index eeafbae..d4f40ae 100644 --- a/tests/test_blag.py +++ b/tests/test_blag.py @@ -1,37 +1,41 @@ from tempfile import TemporaryDirectory import os from datetime import datetime +from typing import Any +from argparse import Namespace import pytest +from pytest import CaptureFixture, LogCaptureFixture +from jinja2 import Template +from blag import __VERSION__ from blag import blag -def test_generate_feed(cleandir): - articles = [] +def test_generate_feed(cleandir: str) -> None: + articles: list[tuple[str, dict[str, Any]]] = [] blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ') assert os.path.exists('build/atom.xml') -def test_feed(cleandir): - articles = [ - [ +def test_feed(cleandir: str) -> None: + articles: list[tuple[str, dict[str, Any]]] = [ + ( 'dest1.html', { 'title': 'title1', 'date': datetime(2019, 6, 6), 'content': 'content1', } - ], - [ + ), + ( 'dest2.html', { 'title': 'title2', 'date': datetime(1980, 5, 9), 'content': 'content2', } - ], - + ), ] blag.generate_feed(articles, 'build', 'https://example.com/', @@ -60,10 +64,10 @@ def test_feed(cleandir): assert ' None: # if a description is provided, it will be used as the summary in # the feed, otherwise we simply use the title of the article - articles = [[ + articles: list[tuple[str, dict[str, Any]]] = [( 'dest.html', { 'title': 'title', @@ -71,7 +75,7 @@ def test_generate_feed_with_description(cleandir): 'date': datetime(2019, 6, 6), 'content': 'content', } - ]] + )] blag.generate_feed(articles, 'build', ' ', ' ', ' ', ' ') with open('build/atom.xml') as fh: @@ -83,7 +87,7 @@ def test_generate_feed_with_description(cleandir): assert 'content' in feed -def test_parse_args_build(): +def test_parse_args_build() -> None: # test default args args = blag.parse_args(['build']) assert args.input_dir == 'content' @@ -116,7 +120,7 @@ def test_parse_args_build(): assert args.static_dir == 'foo' -def test_get_config(): +def test_get_config() -> None: config = """ [main] base_url = https://example.com/ @@ -166,8 +170,8 @@ author = a. u. thor assert config_parsed['base_url'] == 'https://example.com/' -def test_environment_factory(): - globals_ = { +def test_environment_factory() -> None: + globals_: dict[str, object] = { 'foo': 'bar', 'test': 'me' } @@ -176,7 +180,11 @@ def test_environment_factory(): assert env.globals['test'] == 'me' -def test_process_markdown(cleandir, page_template, article_template): +def test_process_markdown( + cleandir: str, + page_template: Template, + article_template: Template, +) -> None: page1 = """\ title: some page @@ -202,10 +210,9 @@ foo bar convertibles = [] for i, txt in enumerate((page1, article1, article2)): - i = str(i) - with open(f'content/{i}', 'w') as fh: + with open(f'content/{str(i)}', 'w') as fh: fh.write(txt) - convertibles.append([i, i]) + convertibles.append((str(i), str(i))) articles, pages = blag.process_markdown( convertibles, @@ -230,7 +237,7 @@ foo bar assert 'content' in context -def test_build(args): +def test_build(args: Namespace) -> None: page1 = """\ title: some page @@ -259,10 +266,9 @@ foo bar # write some convertibles convertibles = [] for i, txt in enumerate((page1, article1, article2)): - i = str(i) - with open(f'{args.input_dir}/{i}.md', 'w') as fh: + with open(f'{args.input_dir}/{str(i)}.md', 'w') as fh: fh.write(txt) - convertibles.append([i, i]) + convertibles.append((str(i), str(i))) # some static files with open(f'{args.static_dir}/test', 'w') as fh: @@ -291,21 +297,21 @@ foo bar assert os.path.exists(f'{args.output_dir}/tags/bar.html') -def test_main(cleandir): +def test_main(cleandir: str) -> None: blag.main(['build']) -def test_cli_version(capsys): +def test_cli_version(capsys: CaptureFixture[str]) -> None: with pytest.raises(SystemExit) as ex: blag.main(['--version']) # normal system exit assert ex.value.code == 0 # proper version reported out, _ = capsys.readouterr() - assert blag.__VERSION__ in out + assert __VERSION__ in out -def test_cli_verbose(cleandir, caplog): +def test_cli_verbose(cleandir: str, caplog: LogCaptureFixture) -> None: blag.main(['build']) assert 'DEBUG' not in caplog.text diff --git a/tests/test_devserver.py b/tests/test_devserver.py index 7f56a40..cdbb842 100644 --- a/tests/test_devserver.py +++ b/tests/test_devserver.py @@ -1,12 +1,13 @@ import time import threading +from argparse import Namespace import pytest from blag import devserver -def test_get_last_modified(cleandir): +def test_get_last_modified(cleandir: str) -> None: # take initial time t1 = devserver.get_last_modified(['content']) @@ -24,7 +25,7 @@ def test_get_last_modified(cleandir): assert t2 == t3 -def test_autoreload_builds_immediately(args): +def test_autoreload_builds_immediately(args: Namespace) -> None: # create a dummy file that can be build with open('content/test.md', 'w') as fh: fh.write('boo') @@ -45,7 +46,7 @@ def test_autoreload_builds_immediately(args): @pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") # noqa -def test_autoreload(args): +def test_autoreload(args: Namespace) -> None: t = threading.Thread(target=devserver.autoreload, args=(args, ), daemon=True,) diff --git a/tests/test_markdown.py b/tests/test_markdown.py index 9103ec1..4cee8c2 100644 --- a/tests/test_markdown.py +++ b/tests/test_markdown.py @@ -1,4 +1,5 @@ from datetime import datetime +from typing import Any import pytest import markdown @@ -26,7 +27,7 @@ from blag.markdown import convert_markdown, markdown_factory ('[test][]\n[test]: /a/test.md', '/a/test.html'), ('[test][]\n[test]: /a/test.md "test"', '/a/test.html'), ]) -def test_convert_markdown_links(input_, expected): +def test_convert_markdown_links(input_: str, expected: str) -> None: md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html @@ -40,7 +41,7 @@ def test_convert_markdown_links(input_, expected): # no path ('[test]()', ''), ]) -def test_dont_convert_normal_links(input_, expected): +def test_dont_convert_normal_links(input_: str, expected: str) -> None: md = markdown_factory() html, _ = convert_markdown(md, input_) assert expected in html @@ -54,18 +55,18 @@ def test_dont_convert_normal_links(input_, expected): ('date: 2020-01-01 12:10', {'date': datetime(2020, 1, 1, 12, 10).astimezone()}), ]) -def test_convert_metadata(input_, expected): +def test_convert_metadata(input_: str, expected: dict[str, Any]) -> None: md = markdown_factory() _, meta = convert_markdown(md, input_) assert expected == meta -def test_markdown_factory(): +def test_markdown_factory() -> None: md = markdown_factory() assert isinstance(md, markdown.Markdown) -def test_smarty(): +def test_smarty() -> None: md = markdown_factory() md1 = """ @@ -79,7 +80,7 @@ this --- is -- a test ... assert 'hellip' in html -def test_smarty_code(): +def test_smarty_code() -> None: md = markdown_factory() md1 = """ diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 4594497..ada2844 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -1,19 +1,21 @@ +from pytest import MonkeyPatch + from blag.quickstart import get_input, quickstart -def test_get_input_default_answer(monkeypatch): +def test_get_input_default_answer(monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr('builtins.input', lambda x: '') answer = get_input("foo", "bar") assert answer == 'bar' -def test_get_input(monkeypatch): +def test_get_input(monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr('builtins.input', lambda x: 'baz') answer = get_input("foo", "bar") assert answer == 'baz' -def test_quickstart(cleandir, monkeypatch): +def test_quickstart(cleandir: str, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr('builtins.input', lambda x: 'foo') quickstart(None) with open('config.ini', 'r') as fh: diff --git a/tests/test_templates.py b/tests/test_templates.py index e420891..dc3395a 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -1,7 +1,9 @@ import datetime +from jinja2 import Template -def test_page(page_template): + +def test_page(page_template: Template) -> None: ctx = { 'content': 'this is the content', 'title': 'this is the title', @@ -11,7 +13,7 @@ def test_page(page_template): assert 'this is the title' in result -def test_article(article_template): +def test_article(article_template: Template) -> None: ctx = { 'content': 'this is the content', 'title': 'this is the title', @@ -23,7 +25,7 @@ def test_article(article_template): assert '1980-05-09' in result -def test_archive(archive_template): +def test_archive(archive_template: Template) -> None: entry = { 'title': 'this is a title', 'dst': 'https://example.com/link', @@ -41,7 +43,7 @@ def test_archive(archive_template): assert 'https://example.com/link' in result -def test_tags(tags_template): +def test_tags(tags_template: Template) -> None: tags = [('foo', 42)] ctx = { 'tags': tags, @@ -54,7 +56,7 @@ def test_tags(tags_template): assert '42' in result -def test_tag(tag_template): +def test_tag(tag_template: Template) -> None: entry = { 'title': 'this is a title', 'dst': 'https://example.com/link', diff --git a/tests/test_version.py b/tests/test_version.py index 04f8d9a..d978f67 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,5 +1,5 @@ import blag -def test_version(): +def test_version() -> None: assert isinstance(blag.__VERSION__, str)