diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 950b500..4d3e8df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,38 +17,28 @@ jobs: python: ["3"] os: ["ubuntu-latest"] include: - - {python: "3.7", os: "ubuntu-22.04"} - - {python: "3.8", os: "ubuntu-22.04"} - {python: "3.9", os: "ubuntu-22.04"} - {python: "3.10", os: "ubuntu-22.04"} - {python: "3.11", os: "ubuntu-22.04"} - {python: "3.12", os: "ubuntu-22.04"} + - {python: "3.13", os: "ubuntu-24.04"} + - {python: "pypy3.10", os: "ubuntu-24.04"} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: "Set up Python ${{ matrix.python }}" - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python }} - name: "Install dependencies" run: | - # python -m pip install --upgrade pip python -m pip install pytest - python -m pip install mock - python -m pip install flake8 - python -m pip install importlib_metadata - python -m pip install "setuptools>=62" - python -m pip install wheel python -m pip install build - - name: "Lint with flake8" - run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=80 --statistics + python -m pip install "setuptools>=75" - name: "Build and test" run: | python -m build --no-isolation pip install dist/dominate*.tar.gz - pytest + pytest --ignore=tests/community - name: Coveralls env: COVERAGE_RCFILE: ".github/workflows/.coveragerc" @@ -56,5 +46,5 @@ jobs: run: | python -m pip install coverage python -m pip install coveralls - coverage run --source=dominate -m pytest + coverage run --source=dominate -m pytest --ignore=tests/community python -m coveralls --service=github || true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..9edd348 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ + +# 1. Welcome additions + +Anything that is part of the official HTML spec is likely welcome. + +Common patterns of web development, or ease-of-use features are welcome, so long as they are general and are likely to be useful to a broad group and not targetting any specific implimentation. + +## 1.1. Testing + +All PRs must have 100% test coverage of new code. + +New features should include example usage, including motivations. + + + +# 2. Not interested + +For exceptions to these, see #Community + +## 2.2. No 3rd party dependencies + +Do not add 3rd party dependencies. + +## 2.3. No 3rd party integrations + +I am not interested in maintaining integrations with a bunch of random JS/web frameworks/libraries. (i.e. HTMX, Flask, Unpoly, whatever) + + +# 3. Community Packages + +If you wish to add a feature that would otherwise be disallowed by the above, you can make a community package. See `community/htmx.py` for a trivial example. + +Community packages must not be referenced from the main library, and must not do anything unless explicitly imported. + diff --git a/MANIFEST.in b/MANIFEST.in index 11521c7..175a3fa 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,7 @@ include README.md include LICENSE.txt +include CONTRIBUTING.md include pyproject.toml -include setup/setup.py recursive-include tests * global-exclude __pycache__ global-exclude *.pyc diff --git a/Makefile b/Makefile index 1fe0cc0..1518ac4 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,8 @@ test: - -python2 -m pytest . - python3 -m pytest . + python3 -m pytest . --ignore=tests/community -publish_old: clean test - python3 setup/setup.py sdist - python3 setup/setup.py bdist_wheel - python3 -m twine upload dist/* +test-community: + python3 -m pytest . publish: clean test python3 -m build --no-isolation diff --git a/README.md b/README.md index 8a21083..78c5eb2 100644 --- a/README.md +++ b/README.md @@ -556,7 +556,7 @@ print(d) Embedding HTML -------------- -If you need to embed a node of pre-formed HTML coming from a library such as markdown or the like, you can avoid escaped HTML by using the raw method from the dominate.util package: +If you need to embed a node of pre-formed HTML coming from a library such as markdown or the like you can avoid escaped HTML by using the raw method from the dominate.util package: ``` from dominate.util import raw @@ -564,7 +564,7 @@ from dominate.util import raw td(raw('Example')) ``` -Without the raw call, this code would render escaped HTML with lt, etc. +Without the raw call, this code would render escaped HTML with lt, etc. The behavior of the previous block of code is the same as `td_element.innerHTML="Example"` in JavaScript. SVG diff --git a/dominate/__init__.py b/dominate/__init__.py index 50668cb..becc617 100644 --- a/dominate/__init__.py +++ b/dominate/__init__.py @@ -1,4 +1,2 @@ -from ._version import __version__ -version = __version__ - +from .version import __version__, version from .document import document diff --git a/dominate/_version.py b/dominate/_version.py deleted file mode 100644 index b03f5b5..0000000 --- a/dominate/_version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = '2.9.1' diff --git a/dominate/community/htmx.py b/dominate/community/htmx.py new file mode 100644 index 0000000..79c70b1 --- /dev/null +++ b/dominate/community/htmx.py @@ -0,0 +1,12 @@ + +from .. import tags + +class HtmxTag: + @classmethod + def clean_attribute(cls, attribute): + attribute = super().clean_attribute(attribute) + if attribute.startswith('hx_'): + attribute = attribute.replace('_', '-') + return attribute + +tags.html_tag.__bases__ = (HtmxTag,) + tags.html_tag.__bases__ diff --git a/dominate/dom_tag.py b/dominate/dom_tag.py index 44ca8ba..b3becf5 100644 --- a/dominate/dom_tag.py +++ b/dominate/dom_tag.py @@ -23,7 +23,7 @@ from collections import defaultdict, namedtuple from functools import wraps import threading -from asyncio import get_event_loop +from asyncio import get_running_loop from uuid import uuid4 from contextvars import ContextVar @@ -71,7 +71,7 @@ def _get_thread_context(): context.append(("greenlet", greenlet.getcurrent())) try: - if get_event_loop().is_running(): + if get_running_loop().is_running(): # Only add this extra information if we are actually in a running event loop context.append(("async", _get_async_context_id())) # A runtime error is raised if there is no async loop... @@ -441,7 +441,7 @@ def clean_attribute(attribute): }.get(attribute, attribute) # Workaround for Python's reserved words - if attribute[0] == '_': + if attribute[0] == '_' and len(attribute) > 1: attribute = attribute[1:] # Workaround for dash diff --git a/dominate/version.py b/dominate/version.py new file mode 100644 index 0000000..7b8a4b1 --- /dev/null +++ b/dominate/version.py @@ -0,0 +1 @@ +version = __version__ = '3.0.0' diff --git a/pyproject.toml b/pyproject.toml index 8aadde0..cb8f2d2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,3 @@ -[build-system] -requires = ["setuptools>=62"] -build-backend = "setuptools.build_meta" - [project] name = "dominate" description = "Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API." @@ -10,7 +6,7 @@ authors = [ {name = "Tom Flanagan", email = "tom@zkpq.ca"}, {name = "Jake Wharton"}, ] -requires-python = ">=3.4" +requires-python = ">=3.9" keywords = ["framework", "templating", "template", "html", "xhtml", "python", "html5"] license = {text = "LGPL-3.0-or-newer"} classifiers = [ @@ -19,12 +15,11 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "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", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Software Development :: Libraries :: Python Modules", @@ -36,8 +31,13 @@ dynamic = ["version"] Homepage = "https://github.com/Knio/dominate" Source = "https://github.com/Knio/dominate" + +[build-system] +requires = ["setuptools>=75"] +build-backend = "setuptools.build_meta" + [tool.setuptools] packages = ["dominate"] [tool.setuptools.dynamic] -version = {attr = "dominate._version.__version__"} +version = {attr = "dominate.version.version"} diff --git a/setup/setup.py b/setup/setup.py deleted file mode 100644 index 31d6c48..0000000 --- a/setup/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -__license__ = ''' -This file is part of Dominate. - -Dominate is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as -published by the Free Software Foundation, either version 3 of -the License, or (at your option) any later version. - -Dominate is distributed in the hope that it will be useful, but -WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General -Public License along with dominate. If not, see -. -''' -# pylint: disable=bad-whitespace - -from setuptools import setup - -import imp -_version = imp.load_source("dominate._version", "dominate/_version.py") - -long_description = open('README.md').read() - -setup( - name = 'dominate', - version = _version.__version__, - author = 'Tom Flanagan and Jake Wharton', - author_email = 'tom@zkpq.ca', - license = 'LGPLv3', - url = 'https://github.com/Knio/dominate/', - description = 'Dominate is a Python library for creating and manipulating HTML documents using an elegant DOM API.', - long_description = long_description, - long_description_content_type='text/markdown', - keywords = 'framework templating template html xhtml python html5', - - python_requires='>=2.7, <3', - classifiers = [ - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: Implementation :: PyPy', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - 'Topic :: Software Development :: Libraries :: Python Modules', - 'Topic :: Text Processing :: Markup :: HTML', - ], - - packages = ['dominate'], - include_package_data = True, -) diff --git a/tests/community/test_htmx.py b/tests/community/test_htmx.py new file mode 100644 index 0000000..bd0ccd2 --- /dev/null +++ b/tests/community/test_htmx.py @@ -0,0 +1,7 @@ + +from dominate import tags +import dominate.community.htmx + +def test_hx(): + d = tags.div(hx_foo=1) + assert d.render() == '
' diff --git a/tests/test_document.py b/tests/test_document.py index 741f3d1..9f7778d 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -1,6 +1,7 @@ from dominate import document from dominate.tags import * + def test_doc(): d = document() assert d.render() == \ diff --git a/tests/test_dom_tag.py b/tests/test_dom_tag.py index 00dcc9f..3fda86f 100644 --- a/tests/test_dom_tag.py +++ b/tests/test_dom_tag.py @@ -1,8 +1,5 @@ import pytest -try: - import mock -except ImportError: - import unittest.mock as mock +import unittest.mock as mock from dominate.tags import * diff --git a/tests/test_dom_tag_async.py b/tests/test_dom_tag_async.py index 648f9c8..92c2438 100644 --- a/tests/test_dom_tag_async.py +++ b/tests/test_dom_tag_async.py @@ -1,8 +1,9 @@ from asyncio import gather, run, Semaphore -from dominate.dom_tag import async_context_id from textwrap import dedent from dominate import tags +from dominate.dom_tag import async_context_id + # To simulate sleep without making the tests take a hella long time to complete # lets use a pair of semaphores to explicitly control when our coroutines run. @@ -28,7 +29,7 @@ async def merge(): sem_1 = Semaphore(0) sem_2 = Semaphore(0) return await gather( - tag_routine_1(sem_1, sem_2), + tag_routine_1(sem_1, sem_2), tag_routine_2(sem_1, sem_2) ) @@ -67,7 +68,7 @@ async def merge():
""").strip() - + assert tag_2 == dedent("""\
diff --git a/tests/test_dominate.py b/tests/test_dominate.py index 9748eb7..620d058 100644 --- a/tests/test_dominate.py +++ b/tests/test_dominate.py @@ -1,6 +1,6 @@ def test_version(): import dominate - version = '2.9.1' + version = '3.0.0' assert dominate.version == version assert dominate.__version__ == version diff --git a/tests/test_html.py b/tests/test_html.py index fdab1f5..4aacc8c 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -1,12 +1,7 @@ -import dominate -from dominate.tags import * import pytest -from dominate.util import raw -try: - xrange = xrange -except NameError: - xrange = range +from dominate.tags import * +from dominate.util import raw def test_arguments(): @@ -43,7 +38,7 @@ def test_add(): with pytest.raises(ValueError): d += None d += 1 - d += xrange(2,3) + d += range(2,3) d += {'id': 'foo'} assert d.render() == '
12
' assert len(d) == 2 @@ -208,6 +203,9 @@ def test_attributes(): with pytest.raises(AttributeError): x = d['id'] + with div(_foo='a', _='b') as d: + assert d.attributes.keys() == {'foo', '_'} + with div() as d: attr(data_test=False) assert d['data-test'] is False @@ -329,7 +327,7 @@ def test_pretty(): assert p('goodbye ', i('cruel'), ' world').render() == \ '''

goodbye cruel world

''' - + assert p('my 1', sup('st'), ' PR').render() == \ '''

my 1st PR

''' diff --git a/tests/test_svg.py b/tests/test_svg.py index e5bbec3..6e06b49 100644 --- a/tests/test_svg.py +++ b/tests/test_svg.py @@ -1,9 +1,9 @@ +import sys + import dominate.svg from dominate.tags import * from dominate.svg import * -import pytest - def base(): return svg( @@ -14,7 +14,10 @@ def base(): def get_expected(func): - return func.__doc__.replace('\n ', '\n').strip() + doc = func.__doc__ + if sys.version_info < (3, 13): + doc = doc.replace('\n ', '\n') + return doc.strip() def output_test(func):