diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 965b2dd..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,11 +0,0 @@ -include AUTHORS.rst -include CONTRIBUTING.rst -include HISTORY.rst -include LICENSE -include README.rst - -recursive-include tests * -recursive-exclude * __pycache__ -recursive-exclude * *.py[co] - -recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif diff --git a/README.md b/README.md index 3c1012e..4a72902 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,16 @@ esparto [![codecov](https://codecov.io/gh/domvwt/esparto/branch/main/graph/badge.svg?token=35J8NZCUYC)](https://codecov.io/gh/domvwt/esparto) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=domvwt_esparto&metric=alert_status)](https://sonarcloud.io/dashboard?id=domvwt_esparto) -## Introduction +## Introduction `esparto` is a simple HTML and PDF document generator for Python. The library takes a fully Pythonic approach to defining documents, allowing iterative building and modification of the page and its contents. -`esparto` is especially good at: + +### Example Use Cases * Automated MI reporting -* Collating and sharing data visualisation +* Collating and sharing data visualisations * ML model performance and evaluation documents * Designing simple web pages @@ -26,7 +27,7 @@ allowing iterative building and modification of the page and its contents. * Output self-contained HTML and PDF files * Responsive layout from [Bootstrap](https://getbootstrap.com/) * No CSS or HTML required -* Automatic conversion for: +* Implicit conversion for: * Markdown * Images * Pandas DataFrames @@ -48,7 +49,6 @@ pip install weasyprint ## Dependencies - * [python](https://python.org/) >= 3.6 * [jinja2](https://palletsprojects.com/p/jinja/) * [markdown](https://python-markdown.github.io/) @@ -76,7 +76,7 @@ page = es.Page(title="Research") # Add or update content # Keys are used as titles -page["Introduction"]["Part One"]["Item A"] = "lorem ipsum" +page["Introduction"]["Part One"]["Item A"] = "./text/content.md" page["Introduction"]["Part One"]["Item B"] = "./pictures/image1.jpg" # Add content without a title @@ -119,7 +119,6 @@ page.save_pdf("my-page.pdf") ## Example Output - Iris Report - [HTML](https://domvwt.github.io/esparto/examples/iris-report.html) | [PDF](https://domvwt.github.io/esparto/examples/iris-report.pdf) diff --git a/esparto/__init__.py b/esparto/__init__.py index 3272252..313deac 100644 --- a/esparto/__init__.py +++ b/esparto/__init__.py @@ -34,7 +34,7 @@ # Add or update content # Keys are used as titles -page["Introduction"]["Part One"]["Item A"] = "lorem ipsum" +page["Introduction"]["Part One"]["Item A"] = "./text/content.md" page["Introduction"]["Part One"]["Item B"] = "./pictures/image1.jpg" # Add content without a title @@ -87,7 +87,7 @@ __author__ = """Dominic Thorn""" __email__ = "dominic.thorn@gmail.com" -__version__ = "1.1.0" +__version__ = "1.2.0" _MODULE_PATH: _Path = _Path(__file__).parent.absolute() diff --git a/esparto/_adaptors.py b/esparto/_adaptors.py index ba7d9cf..bb2b4d7 100644 --- a/esparto/_adaptors.py +++ b/esparto/_adaptors.py @@ -1,5 +1,6 @@ from functools import singledispatch from mimetypes import guess_type +from pathlib import Path from esparto import _INSTALLED_MODULES from esparto._content import ( @@ -22,7 +23,7 @@ def content_adaptor(content: Content) -> Content: content (Any): Any content to be added to the document. Returns: - Content: Approriately wrapped content. + Content: Appropriately wrapped content. """ if not issubclass(type(content), Content): @@ -32,13 +33,26 @@ def content_adaptor(content: Content) -> Content: @content_adaptor.register(str) def content_adaptor_core(content: str) -> Content: - """Convert markdown or image to Markdown or Image content.""" + """Convert text or image to Markdown or Image content.""" guess = guess_type(content) - if guess and "image" in str(guess[0]): - return Image(content) + if guess and isinstance(guess[0], str): + file_type = guess[0].split("/")[0] + if file_type == "image": + return Image(content) + elif file_type == "text": + content = Path(content).read_text() + else: + raise TypeError(f"{content}: {file_type}") return Markdown(content) +@content_adaptor.register(Path) +def content_adaptor_path(content: Path) -> Content: + """Convert text or image path to Markdown or Image content.""" + content_str = str(content) + return content_adaptor_core(content_str) + + # Function only available if Pandas is installed. if "pandas" in _INSTALLED_MODULES: from pandas.core.frame import DataFrame # type: ignore diff --git a/poetry.lock b/poetry.lock index 0186417..9b06375 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1341,6 +1341,14 @@ optional = false python-versions = "*" version = "1.4.3" +[[package]] +category = "dev" +description = "Typing stubs for Markdown" +name = "types-markdown" +optional = false +python-versions = "*" +version = "3.3.0" + [[package]] category = "main" description = "Backported and Experimental Type Hints for Python 3.5+" @@ -1462,7 +1470,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pyt extras = ["beautifulsoup4", "weasyprint"] [metadata] -content-hash = "cebec711df35d7291b2113bc8e47db30f869bc53f7116b0f058fac7fd4079e85" +content-hash = "951f1e62f2f8a2429593176babc59392c3b4153e9205ea2c472fb9a6e40ec562" lock-version = "1.0" python-versions = ">=3.6.1,<4.0" @@ -2414,6 +2422,10 @@ typed-ast = [ {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] +types-markdown = [ + {file = "types-Markdown-3.3.0.tar.gz", hash = "sha256:b7bf9ce52a9f712c6a59b75350ed435d6fc1f1d5eafed44a321472bf96cf77db"}, + {file = "types_Markdown-3.3.0-py3-none-any.whl", hash = "sha256:cc18cc0fcc2ba8ab3f5233b2945db33e2e68ddad179708b2d2f08da08d33bc4e"}, +] typing-extensions = [ {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, diff --git a/pyproject.toml b/pyproject.toml index 0f74e1d..deb8c07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "esparto" -version = "1.1.0" +version = "1.2.0" description = "Simple HTML and PDF document generator for Python." authors = ["Dominic Thorn "] license = "MIT" @@ -54,6 +54,7 @@ kaleido = "^0.2.1,!=0.2.1.post1" sklearn = "^0.0" shap = "^0.39.0" zipp = "^3.4.1" +types-Markdown = "^3.3.0" [tool.poetry.extras] extras = ["beautifulsoup4", "weasyprint"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 4dd15c3..0000000 --- a/setup.cfg +++ /dev/null @@ -1,25 +0,0 @@ -[bumpversion] -current_version = 0.1.0 -commit = True -tag = True - -[bumpversion:file:setup.py] -search = version='{current_version}' -replace = version='{new_version}' - -[bumpversion:file:esparto/__init__.py] -search = __version__ = '{current_version}' -replace = __version__ = '{new_version}' - -[bdist_wheel] -universal = 1 - -[flake8] -exclude = docs - -[aliases] -# Define setup.py command aliases here -test = pytest - -# [tool:pytest] -# collect_ignore = ['setup.py'] diff --git a/tests/conftest.py b/tests/conftest.py index 17d3e86..6004f9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,6 +37,7 @@ adaptor_list = [ ("this is markdown", co.Markdown), (_irises_path, co.Image), + (Path(_irises_path), co.Image), ] if _EXTRAS: diff --git a/tests/resources/iris-virginica.jpg:Zone.Identifier b/tests/resources/iris-virginica.jpg:Zone.Identifier deleted file mode 100644 index 8d907ba..0000000 --- a/tests/resources/iris-virginica.jpg:Zone.Identifier +++ /dev/null @@ -1,4 +0,0 @@ -[ZoneTransfer] -ZoneId=3 -ReferrerUrl=https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Iris_virginica_2.jpg/512px-Iris_virginica_2.jpg -HostUrl=https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Iris_virginica_2.jpg/512px-Iris_virginica_2.jpg diff --git a/tests/test_adaptors.py b/tests/test_adaptors.py index 5eba95d..797b13b 100644 --- a/tests/test_adaptors.py +++ b/tests/test_adaptors.py @@ -1,9 +1,10 @@ from inspect import getmembers, isfunction, signature +from pathlib import Path, PosixPath import pytest import esparto._adaptors as ad -from esparto._content import Content +from esparto._content import Content, Markdown from tests.conftest import _EXTRAS, adaptor_list @@ -20,6 +21,9 @@ def test_all_adaptors_covered(adaptor_list_fn): adaptor_types.remove(Content) # Can't use abstract base class in a test if _EXTRAS: adaptor_types.remove(ad.BokehObject) # Can't use abstract base class in a test + if PosixPath in test_classes: + test_classes.remove(PosixPath) + test_classes = adaptor_types | {Path} adaptor_types.remove(None) missing = adaptor_types.difference(test_classes) assert not missing, missing @@ -31,6 +35,26 @@ def test_adaptor_text(input_, expected): assert isinstance(output, expected) +def test_adapator_textfile(tmp_path): + d = tmp_path / "sub" + d.mkdir() + p = d / "hello.exe" + CONTENT = "# This is some Markdown content" + p.write_text(CONTENT) + with pytest.raises(TypeError): + ad.content_adaptor(Path(p)) + + +def test_adapator_bad_file(tmp_path): + d = tmp_path / "sub" + d.mkdir() + p = d / "hello.txt" + CONTENT = "# This is some Markdown content" + p.write_text(CONTENT) + assert ad.content_adaptor(Path(p)) == Markdown(CONTENT) + assert ad.content_adaptor(str(p)) == Markdown(CONTENT) + + def test_incorrect_content_rejected(): class FakeClass: def __call__(self): diff --git a/tox.ini b/tox.ini index f9c24e5..b159e2e 100644 --- a/tox.ini +++ b/tox.ini @@ -22,11 +22,10 @@ commands = [testenv:codequal] basepython = python +allowlist_externals = mypy deps = black flake8 - mypy - pytest # needed for type stubs commands = black --check esparto tests flake8 esparto tests