diff --git a/.github/scripts/get_python_versions.py b/.github/scripts/get_python_versions.py deleted file mode 100644 index 8a39145..0000000 --- a/.github/scripts/get_python_versions.py +++ /dev/null @@ -1,22 +0,0 @@ -if __name__ == '__main__': - import json - - import requests - from packaging import version as semver - - stable_versions = requests.get( - 'https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json' - ).json() - - min_version = semver.parse('3.7') - versions = {} - - for version_object in stable_versions: - - version = version_object['version'] - major_and_minor_version = semver.parse('.'.join(version.split('.')[:2])) - - if major_and_minor_version not in versions and major_and_minor_version >= min_version: - versions[major_and_minor_version] = version - - print(json.dumps(list(versions.values()))) # noqa diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 082066e..22b34ce 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,7 +18,7 @@ jobs: id: cache-venv with: path: .venv - key: venv-0 + key: venv-1 - run: | python -m venv .venv --upgrade-deps source .venv/bin/activate @@ -28,7 +28,7 @@ jobs: id: pre-commit-cache with: path: ~/.cache/pre-commit - key: key-0 + key: key-1 - run: | source .venv/bin/activate pre-commit run --all-files @@ -38,7 +38,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [ "3.7", "3.8", "3.9", "3.10" ] + python-version: [ "3.7.14", "3.8.14", "3.9.15", "3.10.8", "3.11.0", "3.12.0-alpha.1" ] steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 @@ -48,20 +48,19 @@ jobs: id: poetry-cache with: path: ~/.local - key: key-0 + key: key-2 - uses: snok/install-poetry@v1 with: virtualenvs-create: false - version: 1.2.0a2 - uses: actions/cache@v2 id: cache-venv with: path: .venv - key: ${{ hashFiles('**/poetry.lock') }}-0 + key: ${{ hashFiles('**/poetry.lock') }}-1 - run: | python -m venv .venv source .venv/bin/activate - pip install -U pip + pip install -U pip wheel poetry install --no-interaction --no-root if: steps.cache-venv.outputs.cache-hit != 'true' - name: Run tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10b4d9f..6493166 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,11 +1,11 @@ repos: - repo: https://github.com/ambv/black - rev: 21.10b0 + rev: 22.10.0 hooks: - id: black args: [ "--quiet" ] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - id: check-ast - id: check-merge-conflict @@ -18,8 +18,8 @@ repos: - id: trailing-whitespace - id: mixed-line-ending - id: trailing-whitespace - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - repo: https://github.com/pycqa/flake8 + rev: 5.0.4 hooks: - id: flake8 additional_dependencies: [ @@ -34,17 +34,19 @@ repos: 'flake8-printf-formatting', 'flake8-type-checking', ] + args: + - '--allow-star-arg-any' - repo: https://github.com/asottile/pyupgrade - rev: v2.29.0 + rev: v3.2.2 hooks: - id: pyupgrade args: [ "--py36-plus", "--py37-plus",'--keep-runtime-typing' ] - repo: https://github.com/pycqa/isort - rev: 5.10.0 + rev: 5.10.1 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.910-1 + rev: v0.991 hooks: - id: mypy additional_dependencies: diff --git a/README.md b/README.md index b026c61..fd610a8 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,9 @@ - - Package version - - - Code coverage - - - Supported Python versions - - - Checked with mypy - - -# Sanity HTML Renderer for Python +[![pypi](https://img.shields.io/pypi/v/portabletext-html.svg)](https://pypi.org/project/portabletext-html/) +[![test](https://github.com/otovo/python-portabletext-html/actions/workflows/test.yml/badge.svg)](https://github.com/otovo/python-portabletext-html/actions/workflows/test.yml) +[![code coverage](https://codecov.io/gh/otovo/python-portabletext-html/branch/main/graph/badge.svg)](https://codecov.io/gh/otovo/python-portabletext-html) +[![supported python versions](https://img.shields.io/badge/python-3.7%2B-blue)](https://pypi.org/project/python-portabletext-html/) + +# Portable Text HTML Renderer for Python This package generates HTML from [Portable Text](https://github.com/portabletext/portabletext). @@ -20,19 +12,19 @@ For the most part, it mirrors [Sanity's](https://www.sanity.io/) own [block-cont ## Installation ``` -pip install sanity-html +pip install portabletext-html ``` ## Usage -Instantiate the `SanityBlockRenderer` class with your content and call the `render` method. +Instantiate the `PortableTextRenderer` class with your content and call the `render` method. The following content ```python -from sanity_html import SanityBlockRenderer +from portabletext_html import PortableTextRenderer -renderer = SanityBlockRenderer({ +renderer = PortableTextRenderer({ "_key": "R5FvMrjo", "_type": "block", "children": [ @@ -64,26 +56,26 @@ would like to. To illustrate, if you passed this data to the renderer class: ```python -from sanity_html import SanityBlockRenderer - -renderer = SanityBlockRenderer({ - "_type": "block", - "_key": "foo", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Press, " - }, - { - "_type": "button", - "text": "here" - }, - { - "_type": "span", - "text": ", now!" - } - ] +from portabletext_html import PortableTextRenderer + +renderer = PortableTextRenderer({ + "_type": "block", + "_key": "foo", + "style": "normal", + "children": [ + { + "_type": "span", + "text": "Press, " + }, + { + "_type": "button", + "text": "here" + }, + { + "_type": "span", + "text": ", now!" + } + ] }) renderer.render() ``` @@ -94,12 +86,14 @@ does not have a corresponding built-in type serializer by default. To render this text you must provide your own serializer, like this: ```python -from sanity_html import SanityBlockRenderer +from portabletext_html import PortableTextRenderer + def button_serializer(node: dict, context: Optional[Block], list_item: bool): return f'' -renderer = SanityBlockRenderer( + +renderer = PortableTextRenderer( ..., custom_serializers={'button': button_serializer} ) @@ -136,9 +130,9 @@ Like with custom type serializers, additional serializers for marker definitions and styles can be passed in like this: ```python -from sanity_html import SanityBlockRenderer +from portabletext_html import PortableTextRenderer -renderer = SanityBlockRenderer( +renderer = PortableTextRenderer( ..., custom_marker_definitions={'em': ComicSansEmphasis} ) @@ -152,7 +146,7 @@ Here's an example of a custom style, adding an extra font to the built-in equivalent serializer: ```python -from sanity_html.marker_definitions import MarkerDefinition +from portabletext_html.marker_definitions import MarkerDefinition class ComicSansEmphasis(MarkerDefinition): @@ -166,6 +160,11 @@ class ComicSansEmphasis(MarkerDefinition): def render_suffix(cls, span: Span, marker: str, context: Block) -> str: return f'' + @classmethod + def render_text(cls, span: Span, marker: str, context: Block) -> str: + # custom rendering logic can be placed here + return str(span.text) + @classmethod def render(cls, span: Span, marker: str, context: Block) -> str: result = cls.render_prefix(span, marker, context) @@ -178,8 +177,9 @@ Since the `render_suffix` and `render` methods here are actually identical to th they do not need to be specified, and the whole example can be reduced to: ```python -from sanity_html.marker_definitions import MarkerDefinition # base -from sanity_html import SanityBlockRenderer +from portabletext_html.marker_definitions import MarkerDefinition # base +from portabletext_html import PortableTextRenderer + class ComicSansEmphasis(MarkerDefinition): tag = 'em' @@ -189,7 +189,7 @@ class ComicSansEmphasis(MarkerDefinition): return f'<{cls.tag} style="font-family: "Comic Sans MS", "Comic Sans", cursive;">' -renderer = SanityBlockRenderer( +renderer = PortableTextRenderer( ..., custom_marker_definitions={'em': ComicSansEmphasis} ) @@ -220,4 +220,4 @@ In the meantime, users should be able to serialize image types by passing a cust Contributions are always appreciated 👏 -For details, see the [CONTRIBUTING.md](https://github.com/otovo/python-sanity-html/blob/main/CONTRIBUTING.md). +For details, see the [CONTRIBUTING.md](https://github.com/otovo/python-portabletext-html/blob/main/CONTRIBUTING.md). diff --git a/poetry.lock b/poetry.lock index 15e5029..b05bfd9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,6 +1,6 @@ [[package]] name = "atomicwrites" -version = "1.4.0" +version = "1.4.1" description = "Atomic file writes." category = "dev" optional = false @@ -8,25 +8,25 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "attrs" -version = "21.2.0" +version = "22.1.0" description = "Classes Without Boilerplate" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.5" [package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests_no_zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] [[package]] name = "colorama" -version = "0.4.4" +version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" [[package]] name = "coverage" @@ -55,20 +55,20 @@ pyflakes = ">=2.3.0,<2.4.0" [[package]] name = "importlib-metadata" -version = "4.8.1" +version = "5.0.0" description = "Read metadata from Python packages" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} zipp = ">=0.5" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] perf = ["ipython"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "iniconfig" @@ -88,14 +88,14 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.2" +version = "21.3" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2,<3" +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" [[package]] name = "pluggy" @@ -114,11 +114,11 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pycodestyle" @@ -138,11 +138,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pyparsing" -version = "2.4.7" -description = "Python parsing module" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" category = "dev" optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" @@ -180,7 +183,7 @@ pytest = ">=4.6" toml = "*" [package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] [[package]] name = "toml" @@ -192,23 +195,23 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "typing-extensions" -version = "3.10.0.2" -description = "Backported and Experimental Type Hints for Python 3.5+" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.7" [[package]] name = "zipp" -version = "3.6.0" +version = "3.10.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" [package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] [metadata] lock-version = "1.1" @@ -217,16 +220,15 @@ content-hash = "c641d950bccb6ffac52cf3fcd3571b51f5e31d4864c03e763fe2748919bf855b [metadata.files] atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, ] attrs = [ - {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, - {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, ] colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] coverage = [ {file = "coverage-5.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf"}, @@ -287,8 +289,8 @@ flake8 = [ {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"}, - {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, + {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"}, + {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, @@ -299,16 +301,16 @@ mccabe = [ {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] packaging = [ - {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, - {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, ] pluggy = [ {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pycodestyle = [ {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, @@ -319,8 +321,8 @@ pyflakes = [ {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, ] pyparsing = [ - {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, - {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, ] pytest = [ {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, @@ -335,11 +337,10 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] typing-extensions = [ - {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, - {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, - {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, ] zipp = [ - {file = "zipp-3.6.0-py3-none-any.whl", hash = "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"}, - {file = "zipp-3.6.0.tar.gz", hash = "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832"}, + {file = "zipp-3.10.0-py3-none-any.whl", hash = "sha256:4fcb6f278987a6605757302a6e40e896257570d11c51628968ccb2a47e80c6c1"}, + {file = "zipp-3.10.0.tar.gz", hash = "sha256:7a7262fd930bd3e36c50b9a64897aec3fafff3dfdeec9623ae22b40e93f99bb8"}, ] diff --git a/portabletext_html/__init__.py b/portabletext_html/__init__.py new file mode 100644 index 0000000..bd2f0d5 --- /dev/null +++ b/portabletext_html/__init__.py @@ -0,0 +1,3 @@ +from portabletext_html.renderer import PortableTextRenderer, render + +__all__ = ['PortableTextRenderer', 'render'] diff --git a/sanity_html/constants.py b/portabletext_html/constants.py similarity index 88% rename from sanity_html/constants.py rename to portabletext_html/constants.py index bc73e2d..f5ead44 100644 --- a/sanity_html/constants.py +++ b/portabletext_html/constants.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from sanity_html.marker_definitions import ( +from portabletext_html.marker_definitions import ( CodeMarkerDefinition, CommentMarkerDefinition, EmphasisMarkerDefinition, @@ -15,7 +15,7 @@ if TYPE_CHECKING: from typing import Dict, Type - from sanity_html.marker_definitions import MarkerDefinition + from portabletext_html.marker_definitions import MarkerDefinition STYLE_MAP = { 'h1': 'h1', diff --git a/sanity_html/logger.py b/portabletext_html/logger.py similarity index 85% rename from sanity_html/logger.py rename to portabletext_html/logger.py index 7d9ea67..18122b3 100644 --- a/sanity_html/logger.py +++ b/portabletext_html/logger.py @@ -6,7 +6,7 @@ """ import logging -logger = logging.getLogger('sanity_html') +logger = logging.getLogger('portabletext_html') if not logger.handlers: # pragma: no cover logger.setLevel(logging.WARNING) diff --git a/sanity_html/marker_definitions.py b/portabletext_html/marker_definitions.py similarity index 90% rename from sanity_html/marker_definitions.py rename to portabletext_html/marker_definitions.py index 7a7bb05..396f4e1 100644 --- a/sanity_html/marker_definitions.py +++ b/portabletext_html/marker_definitions.py @@ -2,12 +2,12 @@ from typing import TYPE_CHECKING -from sanity_html.logger import logger +from portabletext_html.logger import logger if TYPE_CHECKING: from typing import Type - from sanity_html.types import Block, Span + from portabletext_html.types import Block, Span class MarkerDefinition: @@ -37,10 +37,15 @@ def render_suffix(cls: Type[MarkerDefinition], span: Span, marker: str, context: def render(cls: Type[MarkerDefinition], span: Span, marker: str, context: Block) -> str: """Render the marked span directly with prefix and suffix.""" result = cls.render_prefix(span, marker, context) - result += str(span.text) + result += cls.render_text(span, marker, context) result += cls.render_suffix(span, marker, context) return result + @classmethod + def render_text(cls: Type[MarkerDefinition], span: Span, marker: str, context: Block) -> str: + """Render the content part for a marked span.""" + return str(span.text) + # Decorators diff --git a/sanity_html/py.typed b/portabletext_html/py.typed similarity index 100% rename from sanity_html/py.typed rename to portabletext_html/py.typed diff --git a/sanity_html/renderer.py b/portabletext_html/renderer.py similarity index 84% rename from sanity_html/renderer.py rename to portabletext_html/renderer.py index 67c2577..1ca2ce3 100644 --- a/sanity_html/renderer.py +++ b/portabletext_html/renderer.py @@ -3,16 +3,16 @@ import html from typing import TYPE_CHECKING, cast -from sanity_html.constants import STYLE_MAP -from sanity_html.logger import logger -from sanity_html.marker_definitions import DefaultMarkerDefinition -from sanity_html.types import Block, Span -from sanity_html.utils import get_list_tags, is_block, is_list, is_span +from portabletext_html.constants import STYLE_MAP +from portabletext_html.logger import logger +from portabletext_html.marker_definitions import DefaultMarkerDefinition +from portabletext_html.types import Block, Span +from portabletext_html.utils import get_list_tags, is_block, is_list, is_span if TYPE_CHECKING: - from typing import Callable, Dict, List, Optional, Type, Union + from typing import Any, Callable, Dict, List, Optional, Type, Union - from sanity_html.marker_definitions import MarkerDefinition + from portabletext_html.marker_definitions import MarkerDefinition class UnhandledNodeError(Exception): @@ -32,14 +32,14 @@ class MissingSerializerError(UnhandledNodeError): pass -class SanityBlockRenderer: - """HTML renderer for Sanity block content.""" +class PortableTextRenderer: + """HTML renderer for Sanity's portable text format.""" def __init__( self, blocks: Union[list[dict], dict], - custom_marker_definitions: dict[str, Type[MarkerDefinition]] = None, - custom_serializers: dict[str, Callable[[dict, Optional[Block], bool], str]] = None, + custom_marker_definitions: dict[str, Type[MarkerDefinition]] | None = None, + custom_serializers: dict[str, Callable[[dict, Optional[Block], bool], str]] | None = None, ) -> None: logger.debug('Initializing block renderer') self._wrapper_element: Optional[str] = None @@ -66,7 +66,7 @@ def render(self) -> str: if list_nodes and not is_list(node): tree = self._normalize_list_tree(list_nodes) - result += ''.join([self._render_node(n, Block(**node), list_item=True) for n in tree]) + result += ''.join([self._render_node(n, list_item=True) for n in tree]) list_nodes = [] # reset list_nodes if is_list(node): @@ -106,14 +106,14 @@ def _render_node(self, node: dict, context: Optional[Block] = None, list_item: b elif is_span(node): logger.debug('Rendering node as span') span = Span(**node) - context = cast(Block, context) # context should always be a Block here + context = cast('Block', context) # context should always be a Block here return self._render_span(span, block=context) elif self._custom_serializers.get(node.get('_type', '')): return self._custom_serializers.get(node.get('_type', ''))(node, context, list_item) # type: ignore else: - if hasattr(node, '_type'): + if '_type' in node: raise MissingSerializerError( f'Found unhandled node type: {node["_type"]}. ' 'Most likely this requires a custom serializer.' ) @@ -150,7 +150,19 @@ def _render_span(self, span: Span, block: Block) -> str: marker_callable = block.marker_definitions.get(mark, DefaultMarkerDefinition)() result += marker_callable.render_prefix(span, mark, block) - result += html.escape(span.text).replace('\n', '
') + # to avoid rendering the text multiple times, + # only the first custom mark will be used + custom_mark_text_rendered = False + if sorted_marks: + for mark in sorted_marks: + if custom_mark_text_rendered or mark in prev_marks: + continue + marker_callable = block.marker_definitions.get(mark, DefaultMarkerDefinition)() + result += marker_callable.render_text(span, mark, block) + custom_mark_text_rendered = True + + if not custom_mark_text_rendered: + result += html.escape(span.text).replace('\n', '
') for mark in reversed(sorted_marks): if mark in next_marks: @@ -244,7 +256,7 @@ def _list_from_block(self, block: dict) -> dict: } -def render(blocks: List[Dict], *args, **kwargs) -> str: +def render(blocks: List[Dict], *args: Any, **kwargs: Any) -> str: """Shortcut function inspired by Sanity's own blocksToHtml.h callable.""" - renderer = SanityBlockRenderer(blocks, *args, **kwargs) + renderer = PortableTextRenderer(blocks, *args, **kwargs) return renderer.render() diff --git a/sanity_html/types.py b/portabletext_html/types.py similarity index 80% rename from sanity_html/types.py rename to portabletext_html/types.py index c24f165..898d61e 100644 --- a/sanity_html/types.py +++ b/portabletext_html/types.py @@ -1,14 +1,14 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import TYPE_CHECKING, cast +from typing import TYPE_CHECKING -from sanity_html.utils import get_default_marker_definitions +from portabletext_html.utils import get_default_marker_definitions if TYPE_CHECKING: from typing import Literal, Optional, Tuple, Type, Union - from sanity_html.marker_definitions import MarkerDefinition + from portabletext_html.marker_definitions import MarkerDefinition @dataclass(frozen=True) @@ -53,9 +53,7 @@ def __post_init__(self) -> None: To make handling of span `marks` simpler, we define marker_definitions as a dict, from which we can directly look up both annotation marks or decorator marks. """ - marker_definitions = get_default_marker_definitions(self.markDefs) - marker_definitions.update(self.marker_definitions) - self.marker_definitions = marker_definitions + self.marker_definitions = self._add_custom_marker_definitions() self.marker_frequencies = self._compute_marker_frequencies() def _compute_marker_frequencies(self) -> dict[str, int]: @@ -68,16 +66,24 @@ def _compute_marker_frequencies(self) -> dict[str, int]: counts[mark] = 0 return counts + def _add_custom_marker_definitions(self) -> dict[str, Type[MarkerDefinition]]: + marker_definitions = get_default_marker_definitions(self.markDefs) + marker_definitions.update(self.marker_definitions) + for definition in self.markDefs: + if definition['_type'] in self.marker_definitions: + marker = self.marker_definitions[definition['_type']] + marker_definitions[definition['_key']] = marker + # del marker_definitions[definition['_type']] + return marker_definitions + def get_node_siblings(self, node: Union[dict, Span]) -> Tuple[Optional[dict], Optional[dict]]: """Return the sibling nodes (prev, next) to the given node.""" if not self.children: return None, None try: if type(node) == dict: - node = cast(dict, node) node_idx = self.children.index(node) elif type(node) == Span: - node = cast(Span, node) for index, item in enumerate(self.children): if 'text' in item and node.text == item['text']: # Is it possible to handle several identical texts? @@ -88,11 +94,9 @@ def get_node_siblings(self, node: Union[dict, Span]) -> Tuple[Optional[dict], Op except ValueError: return None, None - prev_node = None next_node = None - if node_idx != 0: - prev_node = self.children[node_idx - 1] + prev_node = self.children[node_idx - 1] if node_idx != 0 else None if node_idx != len(self.children) - 1: next_node = self.children[node_idx + 1] diff --git a/sanity_html/utils.py b/portabletext_html/utils.py similarity index 90% rename from sanity_html/utils.py rename to portabletext_html/utils.py index d0afec7..3977d81 100644 --- a/sanity_html/utils.py +++ b/portabletext_html/utils.py @@ -2,12 +2,12 @@ from typing import TYPE_CHECKING -from sanity_html.constants import ANNOTATION_MARKER_DEFINITIONS, DECORATOR_MARKER_DEFINITIONS +from portabletext_html.constants import ANNOTATION_MARKER_DEFINITIONS, DECORATOR_MARKER_DEFINITIONS if TYPE_CHECKING: from typing import Type - from sanity_html.marker_definitions import MarkerDefinition + from portabletext_html.marker_definitions import MarkerDefinition def get_default_marker_definitions(mark_defs: list[dict]) -> dict[str, Type[MarkerDefinition]]: diff --git a/pyproject.toml b/pyproject.toml index 15152a2..7dc039a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] -name = 'sanity-html' -version = '1.0.0' +name = 'portabletext-html' +version = '1.1.3' description = "HTML renderer for Sanity's Portable Text format" homepage = 'https://github.com/otovo/python-sanity-html' repository = 'https://github.com/otovo/python-sanity-html' @@ -10,7 +10,7 @@ license = 'Apache2' readme = 'README.md' keywords = ['sanity', 'portable', 'text', 'html', 'parsing'] include = ['CHANGELOG.md'] -packages = [{ include = 'sanity_html' }] +packages = [{ include = 'portabletext_html' }] classifiers = [ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', @@ -21,8 +21,11 @@ classifiers = [ 'Topic :: Text Processing :: Markup', 'Topic :: Text Processing :: Markup :: HTML', 'Programming Language :: Python', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Typing :: Typed', ] @@ -51,11 +54,11 @@ include_trailing_comma = true line_length = 120 [tool.pytest.ini_options] -addopts = ['--cov=sanity_html','--cov-report', 'term-missing'] +addopts = ['--cov=portabletext_html','--cov-report', 'term-missing'] markers = ['unsupported'] [tool.coverage.run] -source = ['sanity_html/*'] +source = ['portabletext_html/*'] omit = [] branch = true diff --git a/sanity_html/__init__.py b/sanity_html/__init__.py deleted file mode 100644 index 879ae73..0000000 --- a/sanity_html/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Python Sanity HTML Renderer.""" - -from sanity_html.renderer import SanityBlockRenderer, render - -__all__ = ['SanityBlockRenderer', 'render'] diff --git a/setup.cfg b/setup.cfg index 72028f4..44e5250 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,3 +47,21 @@ exclude = max-complexity = 15 max-line-length = 120 + +[mypy] +show_error_codes = True +warn_unused_ignores = True +strict_optional = True +incremental = True +ignore_missing_imports = True +warn_redundant_casts = True +warn_unused_configs = True +disallow_untyped_defs = True +disallow_untyped_calls = True +local_partial_types = True +show_traceback = True +exclude = + .venv/ + +[mypy-tests.*] +ignore_errors = True diff --git a/tests/fixtures/custom_serializer_node_after_list.json b/tests/fixtures/custom_serializer_node_after_list.json new file mode 100644 index 0000000..39386cf --- /dev/null +++ b/tests/fixtures/custom_serializer_node_after_list.json @@ -0,0 +1,20 @@ +[ + { + "_key": "e5b6e416e6e9", + "_type": "block", + "children": [ + { "_key": "3bbbff0f158b", "_type": "span", "marks": [], "text": "resers" } + ], + "level": 1, + "listItem": "bullet", + "markDefs": [], + "style": "normal" + }, + { + "_key": "73405dda68e0", + "_type": "extraInfoBlock", + "extraInfo": "This informations is not supported by Block", + "markDefs": [], + "style": "normal" + } +] diff --git a/tests/fixtures/invalid_node.json b/tests/fixtures/invalid_node.json new file mode 100644 index 0000000..74f09bf --- /dev/null +++ b/tests/fixtures/invalid_node.json @@ -0,0 +1,13 @@ +{ + "_key": "73405dda68e7", + "children": [ + { + "_key": "25a09c61d80a", + "_type": "span", + "marks": [], + "text": "Otovo guarantee is good" + } + ], + "markDefs": [], + "style": "normal" +} diff --git a/tests/fixtures/invalid_type.json b/tests/fixtures/invalid_type.json new file mode 100644 index 0000000..745ac66 --- /dev/null +++ b/tests/fixtures/invalid_type.json @@ -0,0 +1,14 @@ +{ + "_key": "73405dda68e7", + "_type": "invalid_type", + "children": [ + { + "_key": "25a09c61d80a", + "_type": "span", + "marks": [], + "text": "Otovo guarantee is good" + } + ], + "markDefs": [], + "style": "normal" +} diff --git a/tests/test_marker_definitions.py b/tests/test_marker_definitions.py index 0a2577c..2677218 100644 --- a/tests/test_marker_definitions.py +++ b/tests/test_marker_definitions.py @@ -1,5 +1,8 @@ -from sanity_html import SanityBlockRenderer -from sanity_html.marker_definitions import ( +# pylint: skip-file +from typing import Type + +from portabletext_html import PortableTextRenderer +from portabletext_html.marker_definitions import ( CommentMarkerDefinition, EmphasisMarkerDefinition, LinkMarkerDefinition, @@ -7,7 +10,7 @@ StrongMarkerDefinition, UnderlineMarkerDefinition, ) -from sanity_html.types import Block, Span +from portabletext_html.types import Block, Span sample_texts = ['test', None, 1, 2.2, '!"#$%&/()'] @@ -16,6 +19,7 @@ def test_render_emphasis_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) + assert EmphasisMarkerDefinition.render_text(node, 'em', block) == f'{text}' assert EmphasisMarkerDefinition.render(node, 'em', block) == f'{text}' @@ -23,6 +27,7 @@ def test_render_strong_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) + assert StrongMarkerDefinition.render_text(node, 'strong', block) == f'{text}' assert StrongMarkerDefinition.render(node, 'strong', block) == f'{text}' @@ -30,6 +35,7 @@ def test_render_underline_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) + assert UnderlineMarkerDefinition.render_text(node, 'u', block) == f'{text}' assert ( UnderlineMarkerDefinition.render(node, 'u', block) == f'{text}' @@ -40,6 +46,7 @@ def test_render_strikethrough_marker_success(): for text in sample_texts: node = Span(_type='span', text=text) block = Block(_type='block', children=[node.__dict__]) + assert StrikeThroughMarkerDefinition.render_text(node, 'strike', block) == f'{text}' assert StrikeThroughMarkerDefinition.render(node, 'strike', block) == f'{text}' @@ -49,6 +56,7 @@ def test_render_link_marker_success(): block = Block( _type='block', children=[node.__dict__], markDefs=[{'_type': 'link', '_key': 'linkId', 'href': text}] ) + assert LinkMarkerDefinition.render_text(node, 'linkId', block) == f'{text}' assert LinkMarkerDefinition.render(node, 'linkId', block) == f'{text}' @@ -60,21 +68,34 @@ def test_render_comment_marker_success(): def test_custom_marker_definition(): - from sanity_html.marker_definitions import MarkerDefinition + from portabletext_html.marker_definitions import MarkerDefinition - class ComicSansEmphasis(MarkerDefinition): + class ConditionalMarkerDefinition(MarkerDefinition): tag = 'em' @classmethod - def render_prefix(cls, span, marker, context): - return f'<{cls.tag} style="font-family: "Comic Sans MS", "Comic Sans", cursive;">' + def render_prefix(cls: Type[MarkerDefinition], span: Span, marker: str, context: Block) -> str: + marker_definition = next((md for md in context.markDefs if md['_key'] == marker), None) + condition = marker_definition.get('cloudCondition', '') + if not condition: + style = 'display: none' + return f'<{cls.tag} style=\"{style}\">' + else: + return super().render_prefix(span, marker, context) + + @classmethod + def render_text(cls: Type[MarkerDefinition], span: Span, marker: str, context: Block) -> str: + marker_definition = next((md for md in context.markDefs if md['_key'] == marker), None) + condition = marker_definition.get('cloudCondition', '') + return span.text if not condition else '' - renderer = SanityBlockRenderer( - { + renderer = PortableTextRenderer( + blocks={ '_type': 'block', - 'children': [{'_key': 'a1ph4', '_type': 'span', 'marks': ['em'], 'text': 'Sanity'}], - 'markDefs': [], + 'children': [{'_key': 'a1ph4', '_type': 'span', 'marks': ['some_id'], 'text': 'Sanity'}], + 'markDefs': [{'_key': 'some_id', '_type': 'contractConditional', 'cloudCondition': False}], }, - custom_marker_definitions={'em': ComicSansEmphasis}, + custom_marker_definitions={'contractConditional': ConditionalMarkerDefinition}, ) - assert renderer.render() == '

Sanity

' + result = renderer.render() + assert result == '

Sanity

' diff --git a/tests/test_module_loading.py b/tests/test_module_loading.py index d1a6523..03fe074 100644 --- a/tests/test_module_loading.py +++ b/tests/test_module_loading.py @@ -6,6 +6,6 @@ def test_module_should_be_importable(): This catches any compilation issue we might have. """ - from sanity_html import SanityBlockRenderer + from portabletext_html import PortableTextRenderer - assert SanityBlockRenderer + assert PortableTextRenderer diff --git a/tests/test_rendering.py b/tests/test_rendering.py index a9eaf8d..1c82309 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -1,8 +1,18 @@ import html import json from pathlib import Path +from typing import Optional -from sanity_html.renderer import render +import pytest + +from portabletext_html.renderer import MissingSerializerError, UnhandledNodeError, render +from portabletext_html.types import Block + + +def extraInfoSerializer(node: dict, context: Optional[Block], list_item: bool) -> str: + extraInfo = node.get('extraInfo') + + return f'

{extraInfo}

' def load_fixture(fixture_name) -> dict: @@ -45,3 +55,22 @@ def test_nested_marks(): fixture = load_fixture('nested_marks.json') output = render(fixture) assert output == '

A word of warning; Sanity is addictive.

' + + +def test_missing_serializer(): + fixture = load_fixture('invalid_type.json') + with pytest.raises(MissingSerializerError): + render(fixture) + + +def test_invalid_node(): + fixture = load_fixture('invalid_node.json') + with pytest.raises(UnhandledNodeError): + render(fixture) + + +def test_custom_serializer_node_after_list(): + fixture = load_fixture('custom_serializer_node_after_list.json') + output = render(fixture, custom_serializers={'extraInfoBlock': extraInfoSerializer}) + + assert output == '

This informations is not supported by Block

' diff --git a/tests/test_upstream_suite.py b/tests/test_upstream_suite.py index 01241bf..08efedf 100644 --- a/tests/test_upstream_suite.py +++ b/tests/test_upstream_suite.py @@ -5,10 +5,10 @@ import pytest -from sanity_html import render -from sanity_html.marker_definitions import LinkMarkerDefinition, MarkerDefinition -from sanity_html.renderer import SanityBlockRenderer -from sanity_html.types import Block, Span +from portabletext_html import render +from portabletext_html.marker_definitions import LinkMarkerDefinition, MarkerDefinition +from portabletext_html.renderer import PortableTextRenderer +from portabletext_html.types import Block, Span def fake_image_serializer(node: dict, context: Optional[Block], list_item: bool): @@ -147,7 +147,7 @@ def test_012_image_support(): fixture_data = get_fixture('fixtures/upstream/012-image-support.json') input_blocks = fixture_data['input'] expected_output = fixture_data['output'] - sbr = SanityBlockRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) + sbr = PortableTextRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) output = sbr.render() assert output == expected_output @@ -156,7 +156,7 @@ def test_013_materialized_image_support(): fixture_data = get_fixture('fixtures/upstream/013-materialized-image-support.json') input_blocks = fixture_data['input'] expected_output = fixture_data['output'] - sbr = SanityBlockRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) + sbr = PortableTextRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) output = sbr.render() assert output == expected_output @@ -230,7 +230,7 @@ def test_022_inline_node(): fixture_data = get_fixture('fixtures/upstream/022-inline-nodes.json') input_blocks = fixture_data['input'] expected_output = fixture_data['output'] - sbr = SanityBlockRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) + sbr = PortableTextRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) output = sbr.render() assert output == expected_output @@ -247,7 +247,7 @@ def test_024_inline_image(): fixture_data = get_fixture('fixtures/upstream/024-inline-images.json') input_blocks = fixture_data['input'] expected_output = fixture_data['output'] - sbr = SanityBlockRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) + sbr = PortableTextRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) output = sbr.render() assert output == expected_output @@ -256,7 +256,7 @@ def test_025_image_with_hotspot(): fixture_data = get_fixture('fixtures/upstream/025-image-with-hotspot.json') input_blocks = fixture_data['input'] expected_output = fixture_data['output'] - sbr = SanityBlockRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) + sbr = PortableTextRenderer(input_blocks, custom_serializers={'image': fake_image_serializer}) output = sbr.render() assert output == expected_output @@ -269,7 +269,7 @@ def test_026_inline_block_with_text(): fixture_data = get_fixture('fixtures/upstream/026-inline-block-with-text.json') input_blocks = fixture_data['input'] expected_output = fixture_data['output'] - sbr = SanityBlockRenderer(input_blocks, custom_serializers={'button': button_serializer}) + sbr = PortableTextRenderer(input_blocks, custom_serializers={'button': button_serializer}) output = sbr.render() assert output == expected_output @@ -328,7 +328,7 @@ def render_prefix(cls, span, marker, context) -> str: result = super().render_prefix(span, marker, context) return result.replace('