diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..fd0ec73 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Initial application of pre-commit (including Black) +77f65d1a2ebbb8593e6968ce1af212693b074c93 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85fbbd4..be916cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,41 +7,21 @@ on: workflow_call: jobs: - beefore: - name: Pre-test checks - runs-on: ubuntu-latest - strategy: - max-parallel: 4 - matrix: - task: - - 'flake8' - - 'towncrier-check' - - 'package' - steps: - # Fetch main branch for comparison, then check out current branch. - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: main - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v4.5.0 - with: - python-version: 3.X - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade setuptools - python -m pip install tox - - name: Run pre-test check - run: | - tox -e ${{ matrix.task }} + pre-commit: + name: Pre-commit checks + uses: beeware/.github/.github/workflows/pre-commit-run.yml@main + + towncrier: + name: Check towncrier + uses: beeware/.github/.github/workflows/towncrier-run.yml@main + + package: + name: Python Package + uses: beeware/.github/.github/workflows/python-package-create.yml@main - python-versions: + unit-tests: name: Python compatibility test - needs: beefore + needs: [pre-commit, towncrier, package] runs-on: ubuntu-latest strategy: matrix: @@ -53,16 +33,28 @@ jobs: experimental: true steps: - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3.3.0 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4.5.0 with: python-version: ${{ matrix.python-version }} - - name: Install dependencies + + - name: Get packages + uses: actions/download-artifact@v3.0.2 + with: + name: ${{ needs.package.outputs.artifact-name }} + path: dist + + - name: Install dev dependencies run: | - python -m pip install --upgrade pip - python -m pip install --upgrade setuptools - python -m pip install tox + # We don't actually want to install travertino; + # we just want the dev extras so we have a known version of tox. + python -m pip install $(ls dist/travertino-*.whl)[dev] + - name: Test run: | - tox -e py + tox -e py --installpkg dist/travertino-*.whl diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml new file mode 100644 index 0000000..2bc6a36 --- /dev/null +++ b/.github/workflows/pre-commit-update.yml @@ -0,0 +1,12 @@ +name: Update pre-commit + +on: + schedule: + - cron: "0 20 * * SUN" # Sunday @ 2000 UTC + workflow_dispatch: + +jobs: + pre-commit-update: + name: Update pre-commit + uses: beeware/.github/.github/workflows/pre-commit-update.yml@main + secrets: inherit diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 793af38..ac8a2a6 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -5,25 +5,18 @@ on: types: published jobs: - publish: + deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install --upgrade setuptools - python -m pip install tox - - name: Build release artefacts - run: | - tox -e package - - name: Publish release - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - tox -e publish + - uses: dsaltares/fetch-gh-release-asset@1.1.0 + with: + version: tags/${{ github.event.release.tag_name }} + # This next line is *not* a bash filename expansion - it's a regex. + file: travertino.* + regex: true + target: dist/ + + - name: Publish release to production PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_PASSWORD }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2e4d332..d1107a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,22 +3,66 @@ name: Create Release on: push: tags: - - 'v*' + - "v*" jobs: + ci: + uses: ./.github/workflows/ci.yml + release: name: Create Release - runs-on: ubuntu-latest + needs: ci + permissions: + contents: write steps: - - name: Checkout code - uses: actions/checkout@master + - name: Set build variables + run: | + echo "VERSION=${GITHUB_REF_NAME#v}" >> $GITHUB_ENV + + - name: Set up Python + uses: actions/setup-python@v4.5.0 + with: + python-version: "3.X" + + - name: Get packages + uses: actions/download-artifact@v3.0.2 + with: + name: packages + path: dist + + - name: Install packages + run: pip install dist/travertino-*.whl + + - name: Check version number + # Check that the setuptools_scm-generated version number is still the same when + # installed from a wheel with setuptools_scm not present. + run: | + set -x + test $(python -c "from travertino import __version__; print(__version__)") = $VERSION + - name: Create Release - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: ncipollo/release-action@v1.12.0 with: - tag_name: ${{ github.ref }} - release_name: ${{ github.ref }} + name: ${{ env.VERSION }} draft: true - prerelease: false + artifacts: dist/* + artifactErrorsFailBuild: true + + test-publish: + name: Publish test package + needs: [release] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Get packages + uses: actions/download-artifact@v3.0.2 + with: + name: packages + path: dist + + - name: Publish release to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository_url: https://test.pypi.org/legacy/ + password: ${{ secrets.TEST_PYPI_PASSWORD }} diff --git a/.gitignore b/.gitignore index f77f0b5..0b5784b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,3 @@ distribute-* venv .idea pip-wheel-metadata - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..afcf057 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,28 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-toml + - id: check-yaml + - id: check-case-conflict + - id: check-docstring-first + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + additional_dependencies: [toml] + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: [--py37-plus] + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09e2781..ec4f1c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,7 @@ # Contributing -PyBee <3's contributions! +BeeWare <3's contributions! -Please be aware, PyBee operates under a Code of Conduct. - -See [CONTRIBUTING to PyBee](http://pybee.org/contributing) for details. +Please be aware, BeeWare operates under a Code of Conduct. +See [CONTRIBUTING to BeeWare](https://beeware.org/contributing) for details. diff --git a/MANIFEST.in b/MANIFEST.in index 2f83e76..77a1dc0 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,9 +3,8 @@ include CONTRIBUTING.md include README.rst include AUTHORS include LICENSE -include beekeeper.yml include tox.ini +include .git-blame-ignore-revs +include .pre-commit-config.yaml recursive-include changes *.rst -recursive-include travertino *.py recursive-include tests *.py -recursive-include tests *.json diff --git a/README.rst b/README.rst index 5b07825..6a856ea 100644 --- a/README.rst +++ b/README.rst @@ -24,7 +24,7 @@ Travertino .. image:: https://img.shields.io/discord/836455665257021440?label=Discord%20Chat&logo=discord&style=plastic :target: https://beeware.org/bee/chat/ :alt: Discord server - + Travertino is a set of constants and utilities for describing user interfaces, including: @@ -74,6 +74,14 @@ Contributing If you experience problems with Travertino, `log them on GitHub`_. If you want to contribute code, please `fork the code`_ and `submit a pull request`_. +Travertino uses `Pre-commit `__ and `TownCrier +`__ to help maintain code quality. For +details on how to use these tools as part of your development environment, see +the `Briefcase code contribution guide +`__. +Although that document is for a different project, the details about setting up +your development environment are the same. + .. _BeeWare suite: http://beeware.org .. _Read The Docs: https://travertino.readthedocs.io .. _@pybeeware on Twitter: https://twitter.com/pybeeware diff --git a/changes/18.doc.rst b/changes/18.doc.rst new file mode 100644 index 0000000..93e004d --- /dev/null +++ b/changes/18.doc.rst @@ -0,0 +1 @@ +Details on towncrier and pre-commit ussage were added to the README. diff --git a/changes/22.misc.rst b/changes/22.misc.rst index 9afbc42..ee0ef39 100644 --- a/changes/22.misc.rst +++ b/changes/22.misc.rst @@ -1 +1 @@ -Corrected some spelling errors. \ No newline at end of file +Corrected some spelling errors. diff --git a/changes/23.feature.rst b/changes/23.feature.rst index 8dc6d78..a22e951 100644 --- a/changes/23.feature.rst +++ b/changes/23.feature.rst @@ -1 +1 @@ -Node now supports the `clear` method in order to clear all children. \ No newline at end of file +Node now supports the ``clear`` method in order to clear all children. diff --git a/changes/24.misc.rst b/changes/24.misc.rst index df20f9a..bf80e14 100644 --- a/changes/24.misc.rst +++ b/changes/24.misc.rst @@ -1 +1 @@ -Flake8 should ignore the venv directory when running. \ No newline at end of file +Flake8 should ignore the venv directory when running. diff --git a/changes/25.misc.rst b/changes/25.misc.rst index 2765652..8a84664 100644 --- a/changes/25.misc.rst +++ b/changes/25.misc.rst @@ -1 +1 @@ -Drop python 3.5 support! \ No newline at end of file +Drop python 3.5 support! diff --git a/changes/26.misc.rst b/changes/26.misc.rst index 48b128a..61bdffa 100644 --- a/changes/26.misc.rst +++ b/changes/26.misc.rst @@ -1 +1 @@ -Upgrade all codebase to use format-strings \ No newline at end of file +Upgrade all codebase to use format-strings diff --git a/changes/34.misc.rst b/changes/34.misc.rst new file mode 100644 index 0000000..890adf3 --- /dev/null +++ b/changes/34.misc.rst @@ -0,0 +1 @@ +Pre-commit was added to the codebase. This introduces black as a coding style. diff --git a/pyproject.toml b/pyproject.toml index 8c96d1c..3829c7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,15 @@ [build-system] -requires = [ - "setuptools >= 43.0.0", - "wheel >= 0.32.0", -] +requires = ["setuptools==66.1.1", "setuptools_scm[toml]==7.0.5"] build-backend = "setuptools.build_meta" +[tool.isort] +profile = "black" +split_on_trailing_comma = true +combine_as_imports = true + +[tool.setuptools_scm] +# To enable SCM versioning, we need an empty tool configuration for setuptools_scm + [tool.towncrier] directory = "changes" package = "travertino" @@ -12,4 +17,3 @@ filename = "CHANGELOG.rst" title_format = "{version} ({project_date})" issue_format = "`#{issue} `_" template = "changes/template.rst" -underlines = ["-", "^", "\""] diff --git a/setup.cfg b/setup.cfg index 64da470..02d30c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,5 @@ [metadata] name = travertino -version = attr: travertino.__version__ project_urls = Funding = https://beeware.org/contributing/membership/ Tracker = https://github.com/beeware/travertino/issues @@ -35,6 +34,16 @@ packages = find: package_dir = = src +[options.extras_require] +dev = + # Pre-commit 3.0 dropped support for Python 3.7 + pre-commit == 2.21.0; python_version < "3.8" + pre-commit == 3.0.4; python_version >= "3.8" + pytest == 7.2.1 + pytest-tldr == 0.2.5 + setuptools_scm[toml] == 7.1.0 + tox == 4.4.4 + [options.packages.find] where = src @@ -53,12 +62,3 @@ max-line-length = 119 # E226: missing whitespace around arithmetic operator # W503: line break occurred before a binary operator ignore = E133,E226,W503 - -# ignore = E121,E123,E126,E226,E24,E704,W503,W504,C901 - -[isort] -combine_as_imports = true -include_trailing_comma = true -line_length = 79 -multi_line_output = 3 -not_skip = __init__.py diff --git a/setup.py b/setup.py deleted file mode 100644 index c823345..0000000 --- a/setup.py +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env python -from setuptools import setup - -setup() diff --git a/src/travertino/__init__.py b/src/travertino/__init__.py index 8379c6f..cafea81 100644 --- a/src/travertino/__init__.py +++ b/src/travertino/__init__.py @@ -1,9 +1,21 @@ -# Examples of valid version strings -# __version__ = '1.2.3.dev1' # Development release 1 -# __version__ = '1.2.3a1' # Alpha Release 1 -# __version__ = '1.2.3b1' # Beta Release 1 -# __version__ = '1.2.3rc1' # RC Release 1 -# __version__ = '1.2.3' # Final Release -# __version__ = '1.2.3.post1' # Post Release 1 +try: + # Read version from SCM metadata + # This will only exist in a development environment + from setuptools_scm import get_version -__version__ = '0.1.3' + # Excluded from coverage because a pure test environment (such as the one + # used by tox in CI) won't have setuptools_scm + __version__ = get_version("../..", relative_to=__file__) # pragma: no cover +except (ModuleNotFoundError, LookupError): + # If setuptools_scm isn't in the environment, the call to import will fail. + # If it *is* in the environment, but the code isn't a git checkout (e.g., + # it's been pip installed non-editable) the call to get_version() will fail. + # If either of these occurs, read version from the installer metadata. + + # importlib.metadata.versoin was added in Python 3.8 + try: + from importlib.metadata import version + except ModuleNotFoundError: + from importlib_metadata import version + + __version__ = version("travertino") diff --git a/src/travertino/colors.py b/src/travertino/colors.py index 8ef9c6c..a6b91ee 100644 --- a/src/travertino/colors.py +++ b/src/travertino/colors.py @@ -11,12 +11,7 @@ def __eq__(self, other): c1 = self.rgba c2 = other.rgba - return ( - c1.r == c2.r - and c1.g == c2.g - and c1.b == c2.b - and c1.a == c2.a - ) + return c1.r == c2.r and c1.g == c2.g and c1.b == c2.b and c1.a == c2.a except AttributeError: return False @@ -40,6 +35,7 @@ def _validate_alpha(cls, value): class rgba(Color): "A representation of an RGBA color" + def __init__(self, r, g, b, a): self._validate_rgb("red", r) self._validate_rgb("green", g) @@ -51,7 +47,7 @@ def __init__(self, r, g, b, a): self.a = a def __hash__(self): - return hash(('RGBA-color', self.r, self.g, self.b, self.a)) + return hash(("RGBA-color", self.r, self.g, self.b, self.a)) def __repr__(self): return f"rgba({self.r}, {self.g}, {self.b}, {self.a})" @@ -67,6 +63,7 @@ def rgba(self): class rgb(rgba): "A representation of an RGB color" + def __init__(self, r, g, b): super().__init__(r, g, b, 1.0) @@ -76,6 +73,7 @@ def __repr__(self): class hsla(Color): "A representation of an HSLA color" + def __init__(self, h, s, l, a=1.0): self._validate_between("hue", h, 0, 360) self._validate_partial("saturation", s) @@ -87,7 +85,7 @@ def __init__(self, h, s, l, a=1.0): self.a = a def __hash__(self): - return hash(('HSLA-color', self.h, self.s, self.l, self.a)) + return hash(("HSLA-color", self.h, self.s, self.l, self.a)) def __repr__(self): return f"hsla({self.h}, {self.s}, {self.l}, {self.a})" @@ -113,15 +111,16 @@ def rgba(self): r, g, b = c + m, m, x + m return rgba( - round(r * 0xff), - round(g * 0xff), - round(b * 0xff), - self.a + round(r * 0xFF), + round(g * 0xFF), + round(b * 0xFF), + self.a, ) class hsl(hsla): "A representation of an HSL color" + def __init__(self, h, s, l): super().__init__(h, s, l, 1.0) @@ -154,7 +153,7 @@ def color(value): return value elif isinstance(value, str): - if value[0] == '#': + if value[0] == "#": if len(value) == 4: return rgb( r=int(value[1] + value[1], 16), @@ -166,7 +165,7 @@ def color(value): r=int(value[1] + value[1], 16), g=int(value[2] + value[2], 16), b=int(value[3] + value[3], 16), - a=int(value[4] + value[4], 16) / 0xff, + a=int(value[4] + value[4], 16) / 0xFF, ) elif len(value) == 7: return rgb( @@ -179,44 +178,55 @@ def color(value): r=int(value[1:3], 16), g=int(value[3:5], 16), b=int(value[5:7], 16), - a=int(value[7:9], 16) / 0xff, + a=int(value[7:9], 16) / 0xFF, ) - elif value.startswith('rgba'): + elif value.startswith("rgba"): try: - values = value[5:-1].split(',') + values = value[5:-1].split(",") if len(values) == 4: - return rgba(int(values[0]), int(values[1]), int(values[2]), float(values[3])) + return rgba( + int(values[0]), + int(values[1]), + int(values[2]), + float( + values[3], + ), + ) except ValueError: pass - elif value.startswith('rgb'): + elif value.startswith("rgb"): try: - values = value[4:-1].split(',') + values = value[4:-1].split(",") if len(values) == 3: - return rgb(int(values[0]), int(values[1]), int(values[2])) + return rgb( + int(values[0]), + int(values[1]), + int(values[2]), + ) except ValueError: pass - elif value.startswith('hsla'): + elif value.startswith("hsla"): try: - values = value[5:-1].split(',') + values = value[5:-1].split(",") if len(values) == 4: return hsla( int(values[0]), - int(values[1].strip().rstrip('%')) / 100.0, - int(values[2].strip().rstrip('%')) / 100.0, - float(values[3]) + int(values[1].strip().rstrip("%")) / 100.0, + int(values[2].strip().rstrip("%")) / 100.0, + float(values[3]), ) except ValueError: pass - elif value.startswith('hsl'): + elif value.startswith("hsl"): try: - values = value[4:-1].split(',') + values = value[4:-1].split(",") if len(values) == 3: return hsl( int(values[0]), - int(values[1].strip().rstrip('%')) / 100.0, - int(values[2].strip().rstrip('%')) / 100.0, + int(values[1].strip().rstrip("%")) / 100.0, + int(values[2].strip().rstrip("%")) / 100.0, ) except ValueError: pass @@ -226,7 +236,7 @@ def color(value): except KeyError: pass - raise ValueError('Unknown color %s' % value) + raise ValueError("Unknown color %s" % value) NAMED_COLOR = { @@ -382,5 +392,12 @@ def color(value): __all__ = [ - 'Color', 'rgba', 'rgb', 'hsla', 'hsl', 'color', 'NAMED_COLOR', 'TRANSPARENT' + "Color", + "rgba", + "rgb", + "hsla", + "hsl", + "color", + "NAMED_COLOR", + "TRANSPARENT", ] + [name.upper() for name in NAMED_COLOR.keys()] diff --git a/src/travertino/constants.py b/src/travertino/constants.py index 93ba53e..24f1bf4 100644 --- a/src/travertino/constants.py +++ b/src/travertino/constants.py @@ -2,60 +2,60 @@ # Common constants ###################################################################### -NORMAL = 'normal' -LEFT = 'left' -RIGHT = 'right' -TOP = 'top' -BOTTOM = 'bottom' -CENTER = 'center' +NORMAL = "normal" +LEFT = "left" +RIGHT = "right" +TOP = "top" +BOTTOM = "bottom" +CENTER = "center" ###################################################################### # Direction ###################################################################### -ROW = 'row' -COLUMN = 'column' +ROW = "row" +COLUMN = "column" ###################################################################### # Visibility ###################################################################### -VISIBLE = 'visible' -HIDDEN = 'hidden' -NONE = 'none' +VISIBLE = "visible" +HIDDEN = "hidden" +NONE = "none" ###################################################################### # Text Justification ###################################################################### -JUSTIFY = 'justify' +JUSTIFY = "justify" ###################################################################### # Text Direction ###################################################################### -RTL = 'rtl' -LTR = 'ltr' +RTL = "rtl" +LTR = "ltr" ###################################################################### # Font family ###################################################################### -SYSTEM = 'system' -MESSAGE = 'message' +SYSTEM = "system" +MESSAGE = "message" -SERIF = 'serif' -SANS_SERIF = 'sans-serif' -CURSIVE = 'cursive' -FANTASY = 'fantasy' -MONOSPACE = 'monospace' +SERIF = "serif" +SANS_SERIF = "sans-serif" +CURSIVE = "cursive" +FANTASY = "fantasy" +MONOSPACE = "monospace" ###################################################################### # Font Styling ###################################################################### -ITALIC = 'italic' -OBLIQUE = 'oblique' +ITALIC = "italic" +OBLIQUE = "oblique" FONT_STYLES = {ITALIC, OBLIQUE} @@ -63,7 +63,7 @@ # Font Variant ###################################################################### -SMALL_CAPS = 'small-caps' +SMALL_CAPS = "small-caps" FONT_VARIANTS = {SMALL_CAPS} @@ -71,7 +71,7 @@ # Font boldness ###################################################################### -BOLD = 'bold' +BOLD = "bold" FONT_WEIGHTS = {BOLD} @@ -85,153 +85,153 @@ # Colors ###################################################################### -TRANSPARENT = 'transparent' - -ALICEBLUE = 'aliceblue' -ANTIQUEWHITE = 'antiquewhite' -AQUA = 'aqua' -AQUAMARINE = 'aquamarine' -AZURE = 'azure' -BEIGE = 'beige' -BISQUE = 'bisque' -BLACK = 'black' -BLANCHEDALMOND = 'blanchedalmond' -BLUE = 'blue' -BLUEVIOLET = 'blueviolet' -BROWN = 'brown' -BURLYWOOD = 'burlywood' -CADETBLUE = 'cadetblue' -CHARTREUSE = 'chartreuse' -CHOCOLATE = 'chocolate' -CORAL = 'coral' -CORNFLOWERBLUE = 'cornflowerblue' -CORNSILK = 'cornsilk' -CRIMSON = 'crimson' -CYAN = 'cyan' -DARKBLUE = 'darkblue' -DARKCYAN = 'darkcyan' -DARKGOLDENROD = 'darkgoldenrod' -DARKGRAY = 'darkgray' -DARKGREY = 'darkgrey' -DARKGREEN = 'darkgreen' -DARKKHAKI = 'darkkhaki' -DARKMAGENTA = 'darkmagenta' -DARKOLIVEGREEN = 'darkolivegreen' -DARKORANGE = 'darkorange' -DARKORCHID = 'darkorchid' -DARKRED = 'darkred' -DARKSALMON = 'darksalmon' -DARKSEAGREEN = 'darkseagreen' -DARKSLATEBLUE = 'darkslateblue' -DARKSLATEGRAY = 'darkslategray' -DARKSLATEGREY = 'darkslategrey' -DARKTURQUOISE = 'darkturquoise' -DARKVIOLET = 'darkviolet' -DEEPPINK = 'deeppink' -DEEPSKYBLUE = 'deepskyblue' -DIMGRAY = 'dimgray' -DIMGREY = 'dimgrey' -DODGERBLUE = 'dodgerblue' -FIREBRICK = 'firebrick' -FLORALWHITE = 'floralwhite' -FORESTGREEN = 'forestgreen' -FUCHSIA = 'fuchsia' -GAINSBORO = 'gainsboro' -GHOSTWHITE = 'ghostwhite' -GOLD = 'gold' -GOLDENROD = 'goldenrod' -GRAY = 'gray' -GREY = 'grey' -GREEN = 'green' -GREENYELLOW = 'greenyellow' -HONEYDEW = 'honeydew' -HOTPINK = 'hotpink' -INDIANRED = 'indianred' -INDIGO = 'indigo' -IVORY = 'ivory' -KHAKI = 'khaki' -LAVENDER = 'lavender' -LAVENDERBLUSH = 'lavenderblush' -LAWNGREEN = 'lawngreen' -LEMONCHIFFON = 'lemonchiffon' -LIGHTBLUE = 'lightblue' -LIGHTCORAL = 'lightcoral' -LIGHTCYAN = 'lightcyan' -LIGHTGOLDENRODYELLOW = 'lightgoldenrodyellow' -LIGHTGRAY = 'lightgray' -LIGHTGREY = 'lightgrey' -LIGHTGREEN = 'lightgreen' -LIGHTPINK = 'lightpink' -LIGHTSALMON = 'lightsalmon' -LIGHTSEAGREEN = 'lightseagreen' -LIGHTSKYBLUE = 'lightskyblue' -LIGHTSLATEGRAY = 'lightslategray' -LIGHTSLATEGREY = 'lightslategrey' -LIGHTSTEELBLUE = 'lightsteelblue' -LIGHTYELLOW = 'lightyellow' -LIME = 'lime' -LIMEGREEN = 'limegreen' -LINEN = 'linen' -MAGENTA = 'magenta' -MAROON = 'maroon' -MEDIUMAQUAMARINE = 'mediumaquamarine' -MEDIUMBLUE = 'mediumblue' -MEDIUMORCHID = 'mediumorchid' -MEDIUMPURPLE = 'mediumpurple' -MEDIUMSEAGREEN = 'mediumseagreen' -MEDIUMSLATEBLUE = 'mediumslateblue' -MEDIUMSPRINGGREEN = 'mediumspringgreen' -MEDIUMTURQUOISE = 'mediumturquoise' -MEDIUMVIOLETRED = 'mediumvioletred' -MIDNIGHTBLUE = 'midnightblue' -MINTCREAM = 'mintcream' -MISTYROSE = 'mistyrose' -MOCCASIN = 'moccasin' -NAVAJOWHITE = 'navajowhite' -NAVY = 'navy' -OLDLACE = 'oldlace' -OLIVE = 'olive' -OLIVEDRAB = 'olivedrab' -ORANGE = 'orange' -ORANGERED = 'orangered' -ORCHID = 'orchid' -PALEGOLDENROD = 'palegoldenrod' -PALEGREEN = 'palegreen' -PALETURQUOISE = 'paleturquoise' -PALEVIOLETRED = 'palevioletred' -PAPAYAWHIP = 'papayawhip' -PEACHPUFF = 'peachpuff' -PERU = 'peru' -PINK = 'pink' -PLUM = 'plum' -POWDERBLUE = 'powderblue' -PURPLE = 'purple' -REBECCAPURPLE = 'rebeccapurple' -RED = 'red' -ROSYBROWN = 'rosybrown' -ROYALBLUE = 'royalblue' -SADDLEBROWN = 'saddlebrown' -SALMON = 'salmon' -SANDYBROWN = 'sandybrown' -SEAGREEN = 'seagreen' -SEASHELL = 'seashell' -SIENNA = 'sienna' -SILVER = 'silver' -SKYBLUE = 'skyblue' -SLATEBLUE = 'slateblue' -SLATEGRAY = 'slategray' -SLATEGREY = 'slategrey' -SNOW = 'snow' -SPRINGGREEN = 'springgreen' -STEELBLUE = 'steelblue' -TAN = 'tan' -TEAL = 'teal' -THISTLE = 'thistle' -TOMATO = 'tomato' -TURQUOISE = 'turquoise' -VIOLET = 'violet' -WHEAT = 'wheat' -WHITE = 'white' -WHITESMOKE = 'whitesmoke' -YELLOW = 'yellow' -YELLOWGREEN = 'yellowgreen' +TRANSPARENT = "transparent" + +ALICEBLUE = "aliceblue" +ANTIQUEWHITE = "antiquewhite" +AQUA = "aqua" +AQUAMARINE = "aquamarine" +AZURE = "azure" +BEIGE = "beige" +BISQUE = "bisque" +BLACK = "black" +BLANCHEDALMOND = "blanchedalmond" +BLUE = "blue" +BLUEVIOLET = "blueviolet" +BROWN = "brown" +BURLYWOOD = "burlywood" +CADETBLUE = "cadetblue" +CHARTREUSE = "chartreuse" +CHOCOLATE = "chocolate" +CORAL = "coral" +CORNFLOWERBLUE = "cornflowerblue" +CORNSILK = "cornsilk" +CRIMSON = "crimson" +CYAN = "cyan" +DARKBLUE = "darkblue" +DARKCYAN = "darkcyan" +DARKGOLDENROD = "darkgoldenrod" +DARKGRAY = "darkgray" +DARKGREY = "darkgrey" +DARKGREEN = "darkgreen" +DARKKHAKI = "darkkhaki" +DARKMAGENTA = "darkmagenta" +DARKOLIVEGREEN = "darkolivegreen" +DARKORANGE = "darkorange" +DARKORCHID = "darkorchid" +DARKRED = "darkred" +DARKSALMON = "darksalmon" +DARKSEAGREEN = "darkseagreen" +DARKSLATEBLUE = "darkslateblue" +DARKSLATEGRAY = "darkslategray" +DARKSLATEGREY = "darkslategrey" +DARKTURQUOISE = "darkturquoise" +DARKVIOLET = "darkviolet" +DEEPPINK = "deeppink" +DEEPSKYBLUE = "deepskyblue" +DIMGRAY = "dimgray" +DIMGREY = "dimgrey" +DODGERBLUE = "dodgerblue" +FIREBRICK = "firebrick" +FLORALWHITE = "floralwhite" +FORESTGREEN = "forestgreen" +FUCHSIA = "fuchsia" +GAINSBORO = "gainsboro" +GHOSTWHITE = "ghostwhite" +GOLD = "gold" +GOLDENROD = "goldenrod" +GRAY = "gray" +GREY = "grey" +GREEN = "green" +GREENYELLOW = "greenyellow" +HONEYDEW = "honeydew" +HOTPINK = "hotpink" +INDIANRED = "indianred" +INDIGO = "indigo" +IVORY = "ivory" +KHAKI = "khaki" +LAVENDER = "lavender" +LAVENDERBLUSH = "lavenderblush" +LAWNGREEN = "lawngreen" +LEMONCHIFFON = "lemonchiffon" +LIGHTBLUE = "lightblue" +LIGHTCORAL = "lightcoral" +LIGHTCYAN = "lightcyan" +LIGHTGOLDENRODYELLOW = "lightgoldenrodyellow" +LIGHTGRAY = "lightgray" +LIGHTGREY = "lightgrey" +LIGHTGREEN = "lightgreen" +LIGHTPINK = "lightpink" +LIGHTSALMON = "lightsalmon" +LIGHTSEAGREEN = "lightseagreen" +LIGHTSKYBLUE = "lightskyblue" +LIGHTSLATEGRAY = "lightslategray" +LIGHTSLATEGREY = "lightslategrey" +LIGHTSTEELBLUE = "lightsteelblue" +LIGHTYELLOW = "lightyellow" +LIME = "lime" +LIMEGREEN = "limegreen" +LINEN = "linen" +MAGENTA = "magenta" +MAROON = "maroon" +MEDIUMAQUAMARINE = "mediumaquamarine" +MEDIUMBLUE = "mediumblue" +MEDIUMORCHID = "mediumorchid" +MEDIUMPURPLE = "mediumpurple" +MEDIUMSEAGREEN = "mediumseagreen" +MEDIUMSLATEBLUE = "mediumslateblue" +MEDIUMSPRINGGREEN = "mediumspringgreen" +MEDIUMTURQUOISE = "mediumturquoise" +MEDIUMVIOLETRED = "mediumvioletred" +MIDNIGHTBLUE = "midnightblue" +MINTCREAM = "mintcream" +MISTYROSE = "mistyrose" +MOCCASIN = "moccasin" +NAVAJOWHITE = "navajowhite" +NAVY = "navy" +OLDLACE = "oldlace" +OLIVE = "olive" +OLIVEDRAB = "olivedrab" +ORANGE = "orange" +ORANGERED = "orangered" +ORCHID = "orchid" +PALEGOLDENROD = "palegoldenrod" +PALEGREEN = "palegreen" +PALETURQUOISE = "paleturquoise" +PALEVIOLETRED = "palevioletred" +PAPAYAWHIP = "papayawhip" +PEACHPUFF = "peachpuff" +PERU = "peru" +PINK = "pink" +PLUM = "plum" +POWDERBLUE = "powderblue" +PURPLE = "purple" +REBECCAPURPLE = "rebeccapurple" +RED = "red" +ROSYBROWN = "rosybrown" +ROYALBLUE = "royalblue" +SADDLEBROWN = "saddlebrown" +SALMON = "salmon" +SANDYBROWN = "sandybrown" +SEAGREEN = "seagreen" +SEASHELL = "seashell" +SIENNA = "sienna" +SILVER = "silver" +SKYBLUE = "skyblue" +SLATEBLUE = "slateblue" +SLATEGRAY = "slategray" +SLATEGREY = "slategrey" +SNOW = "snow" +SPRINGGREEN = "springgreen" +STEELBLUE = "steelblue" +TAN = "tan" +TEAL = "teal" +THISTLE = "thistle" +TOMATO = "tomato" +TURQUOISE = "turquoise" +VIOLET = "violet" +WHEAT = "wheat" +WHITE = "white" +WHITESMOKE = "whitesmoke" +YELLOW = "yellow" +YELLOWGREEN = "yellowgreen" diff --git a/src/travertino/declaration.py b/src/travertino/declaration.py index 5476ae1..5a45641 100644 --- a/src/travertino/declaration.py +++ b/src/travertino/declaration.py @@ -3,9 +3,16 @@ class Choices: "A class to define allowable data types for a property" + def __init__( - self, *constants, default=False, - string=False, integer=False, number=False, color=False): + self, + *constants, + default=False, + string=False, + integer=False, + number=False, + color=False, + ): self.constants = set(constants) self.default = default @@ -14,7 +21,7 @@ def __init__( self.number = number self.color = color - self._options = sorted(str(c).lower().replace('_', '-') for c in self.constants) + self._options = sorted(str(c).lower().replace("_", "-") for c in self.constants) if self.string: self._options.append("") if self.integer: @@ -48,7 +55,7 @@ def validate(self, value): return color(value) except ValueError: pass - if value == 'none': + if value == "none": value = None for const in self.constants: if value == const: @@ -65,6 +72,7 @@ class BaseStyle: Exposes a dict-like interface. """ + _PROPERTIES = {} _ALL_PROPERTIES = {} @@ -77,7 +85,9 @@ def __init__(self, **style): ###################################################################### def apply(self, property, value): - raise NotImplementedError('Style must define an apply method') # pragma: no cover + raise NotImplementedError( + "Style must define an apply method" + ) # pragma: no cover ###################################################################### # Provide a dict-like interface @@ -90,7 +100,7 @@ def reapply(self): def update(self, **styles): "Set multiple styles on the style definition." for name, value in styles.items(): - name = name.replace('-', '_') + name = name.replace("-", "_") if name not in self._ALL_PROPERTIES.get(self.__class__, set()): raise NameError("Unknown style '%s'" % name) @@ -102,26 +112,26 @@ def copy(self, applicator=None): dup._applicator = applicator for style in self._PROPERTIES.get(self.__class__, set()): try: - setattr(dup, style, getattr(self, '_%s' % style)) + setattr(dup, style, getattr(self, "_%s" % style)) except AttributeError: pass return dup def __getitem__(self, name): - name = name.replace('-', '_') + name = name.replace("-", "_") if name in self._PROPERTIES.get(self.__class__, set()): return getattr(self, name) raise KeyError(name) def __setitem__(self, name, value): - name = name.replace('-', '_') + name = name.replace("-", "_") if name in self._PROPERTIES.get(self.__class__, set()): setattr(self, name, value) else: raise KeyError(name) def __delitem__(self, name): - name = name.replace('-', '_') + name = name.replace("-", "_") if name in self._PROPERTIES.get(self.__class__, set()): delattr(self, name) else: @@ -131,7 +141,7 @@ def items(self): result = [] for name in self._PROPERTIES.get(self.__class__, set()): try: - result.append((name, getattr(self, '_%s' % name))) + result.append((name, getattr(self, "_%s" % name))) except AttributeError: pass return result @@ -139,7 +149,7 @@ def items(self): def keys(self): result = set() for name in self._PROPERTIES.get(self.__class__, set()): - if hasattr(self, '_%s' % name): + if hasattr(self, "_%s" % name): result.add(name) return result @@ -150,17 +160,13 @@ def __str__(self): non_default = [] for name in self._PROPERTIES.get(self.__class__, set()): try: - non_default.append(( - name.replace('_', '-'), - getattr(self, '_%s' % name) - )) + non_default.append( + (name.replace("_", "-"), getattr(self, "_%s" % name)) + ) except AttributeError: pass - return "; ".join( - f"{name}: {value}" - for name, value in sorted(non_default) - ) + return "; ".join(f"{name}: {value}" for name, value in sorted(non_default)) @classmethod def validated_property(cls, name, choices, initial=None): @@ -171,24 +177,26 @@ def validated_property(cls, name, choices, initial=None): raise ValueError(f"Invalid initial value '{initial}' for property '{name}'") def getter(self): - return getattr(self, '_%s' % name, initial) + return getattr(self, "_%s" % name, initial) def setter(self, value): try: value = choices.validate(value) except ValueError: - raise ValueError("Invalid value '{}' for property '{}'; Valid values are: {}".format( - value, name, choices - )) + raise ValueError( + "Invalid value '{}' for property '{}'; Valid values are: {}".format( + value, name, choices + ) + ) - if value != getattr(self, '_%s' % name, initial): - setattr(self, '_%s' % name, value) + if value != getattr(self, "_%s" % name, initial): + setattr(self, "_%s" % name, value) self.apply(name, value) def deleter(self): try: - value = getattr(self, '_%s' % name, initial) - delattr(self, '_%s' % name) + value = getattr(self, "_%s" % name, initial) + delattr(self, "_%s" % name) if value != initial: self.apply(name, initial) except AttributeError: @@ -202,56 +210,57 @@ def deleter(self): @classmethod def directional_property(cls, name): "Define a property attribute that proxies for top/right/bottom/left alternatives." + def getter(self): return ( - getattr(self, name % '_top'), - getattr(self, name % '_right'), - getattr(self, name % '_bottom'), - getattr(self, name % '_left'), + getattr(self, name % "_top"), + getattr(self, name % "_right"), + getattr(self, name % "_bottom"), + getattr(self, name % "_left"), ) def setter(self, value): if isinstance(value, tuple): if len(value) == 4: - setattr(self, name % '_top', value[0]) - setattr(self, name % '_right', value[1]) - setattr(self, name % '_bottom', value[2]) - setattr(self, name % '_left', value[3]) + setattr(self, name % "_top", value[0]) + setattr(self, name % "_right", value[1]) + setattr(self, name % "_bottom", value[2]) + setattr(self, name % "_left", value[3]) elif len(value) == 3: - setattr(self, name % '_top', value[0]) - setattr(self, name % '_right', value[1]) - setattr(self, name % '_bottom', value[2]) - setattr(self, name % '_left', value[1]) + setattr(self, name % "_top", value[0]) + setattr(self, name % "_right", value[1]) + setattr(self, name % "_bottom", value[2]) + setattr(self, name % "_left", value[1]) elif len(value) == 2: - setattr(self, name % '_top', value[0]) - setattr(self, name % '_right', value[1]) - setattr(self, name % '_bottom', value[0]) - setattr(self, name % '_left', value[1]) + setattr(self, name % "_top", value[0]) + setattr(self, name % "_right", value[1]) + setattr(self, name % "_bottom", value[0]) + setattr(self, name % "_left", value[1]) elif len(value) == 1: - setattr(self, name % '_top', value[0]) - setattr(self, name % '_right', value[0]) - setattr(self, name % '_bottom', value[0]) - setattr(self, name % '_left', value[0]) + setattr(self, name % "_top", value[0]) + setattr(self, name % "_right", value[0]) + setattr(self, name % "_bottom", value[0]) + setattr(self, name % "_left", value[0]) else: raise ValueError( "Invalid value for '{}'; value must be an number, or a 1-4 tuple.".format( - name % '' + name % "" ) ) else: - setattr(self, name % '_top', value) - setattr(self, name % '_right', value) - setattr(self, name % '_bottom', value) - setattr(self, name % '_left', value) + setattr(self, name % "_top", value) + setattr(self, name % "_right", value) + setattr(self, name % "_bottom", value) + setattr(self, name % "_left", value) def deleter(self): - delattr(self, name % '_top') - delattr(self, name % '_right') - delattr(self, name % '_bottom') - delattr(self, name % '_left') + delattr(self, name % "_top") + delattr(self, name % "_right") + delattr(self, name % "_bottom") + delattr(self, name % "_left") - cls._ALL_PROPERTIES.setdefault(cls, set()).add(name % '') - setattr(cls, name % '', property(getter, setter, deleter)) + cls._ALL_PROPERTIES.setdefault(cls, set()).add(name % "") + setattr(cls, name % "", property(getter, setter, deleter)) # def list_property(name, choices, initial=None): # "Define a property attribute that accepts a list of independently validated values." diff --git a/src/travertino/fonts.py b/src/travertino/fonts.py index df396ed..1fdfc9d 100644 --- a/src/travertino/fonts.py +++ b/src/travertino/fonts.py @@ -13,7 +13,9 @@ class Font: def __init__(self, family, size, style=NORMAL, variant=NORMAL, weight=NORMAL): - if (family[0] == "'" and family[-1] == "'") or (family[0] == '"' and family[-1] == '"'): + if (family[0] == "'" and family[-1] == "'") or ( + family[0] == '"' and family[-1] == '"' + ): self.family = family[1:-1] else: self.family = family @@ -22,7 +24,7 @@ def __init__(self, family, size, style=NORMAL, variant=NORMAL, weight=NORMAL): self.size = int(size) except ValueError: try: - if size.strip().endswith('pt'): + if size.strip().endswith("pt"): self.size = int(size[:-2]) else: raise ValueError(f"Invalid font size {size!r}") @@ -33,15 +35,19 @@ def __init__(self, family, size, style=NORMAL, variant=NORMAL, weight=NORMAL): self.weight = weight if weight in FONT_WEIGHTS else NORMAL def __hash__(self): - return hash(('FONT', self.family, self.size, self.style, self.variant, self.weight)) + return hash( + ("FONT", self.family, self.size, self.style, self.variant, self.weight) + ) def __repr__(self): - return ''.format( - '' if self.style is NORMAL else (self.style + ' '), - '' if self.variant is NORMAL else (self.variant + ' '), - '' if self.weight is NORMAL else (self.weight + ' '), - 'system default size' if self.size == SYSTEM_DEFAULT_FONT_SIZE else f'{self.size}pt', - self.family + return "".format( + "" if self.style is NORMAL else (self.style + " "), + "" if self.variant is NORMAL else (self.variant + " "), + "" if self.weight is NORMAL else (self.weight + " "), + "system default size" + if self.size == SYSTEM_DEFAULT_FONT_SIZE + else f"{self.size}pt", + self.family, ) def __eq__(self, other): @@ -55,31 +61,65 @@ def __eq__(self, other): def normal_style(self): "Generate a normal style version of this font" - return Font(self.family, self.size, style=NORMAL, variant=self.variant, weight=self.weight) + return Font( + self.family, + self.size, + style=NORMAL, + variant=self.variant, + weight=self.weight, + ) def italic(self): "Generate an italic version of this font" - return Font(self.family, self.size, style=ITALIC, variant=self.variant, weight=self.weight) + return Font( + self.family, + self.size, + style=ITALIC, + variant=self.variant, + weight=self.weight, + ) def oblique(self): "Generate an oblique version of this font" - return Font(self.family, self.size, style=OBLIQUE, variant=self.variant, weight=self.weight) + return Font( + self.family, + self.size, + style=OBLIQUE, + variant=self.variant, + weight=self.weight, + ) def normal_variant(self): "Generate a normal variant of this font" - return Font(self.family, self.size, style=self.style, variant=NORMAL, weight=self.weight) + return Font( + self.family, self.size, style=self.style, variant=NORMAL, weight=self.weight + ) def small_caps(self): "Generate a small-caps variant of this font" - return Font(self.family, self.size, style=self.style, variant=SMALL_CAPS, weight=self.weight) + return Font( + self.family, + self.size, + style=self.style, + variant=SMALL_CAPS, + weight=self.weight, + ) def normal_weight(self): "Generate a normal weight version of this font" - return Font(self.family, self.size, style=self.style, variant=self.variant, weight=NORMAL) + return Font( + self.family, + self.size, + style=self.style, + variant=self.variant, + weight=NORMAL, + ) def bold(self): "Generate a bold version of this font" - return Font(self.family, self.size, style=self.style, variant=self.variant, weight=BOLD) + return Font( + self.family, self.size, style=self.style, variant=self.variant, weight=BOLD + ) def font(value): @@ -102,7 +142,7 @@ def font(value): return value elif isinstance(value, str): - parts = value.split(' ') + parts = value.split(" ") style = None variant = None @@ -138,17 +178,17 @@ def font(value): weight = part else: try: - if part.endswith('pt'): + if part.endswith("pt"): size = int(part[:-2]) else: size = int(part) except ValueError: raise ValueError(f"Invalid size in font declaration '{value}'") - if parts[0] == 'pt': + if parts[0] == "pt": parts.pop(0) - family = ' '.join(parts) + family = " ".join(parts) return Font(family, size, style=style, variant=variant, weight=weight) raise ValueError("Unknown font '%s'" % value) diff --git a/src/travertino/layout.py b/src/travertino/layout.py index b8e3af7..b8f656c 100644 --- a/src/travertino/layout.py +++ b/src/travertino/layout.py @@ -4,6 +4,7 @@ class Viewport: rendered. It stores the size of the surface(in pixels), plus the pixel density of the viewport. """ + def __init__(self, width=0, height=0, dpi=None): self.width = width self.height = height @@ -45,10 +46,12 @@ def __init__(self, node): self._reset() def __repr__(self): - return '<{} ({}x{} @ {},{})>'.format( + return "<{} ({}x{} @ {},{})>".format( self.__class__.__name__, - self.content_width, self.content_height, - self.absolute_content_left, self.absolute_content_top, + self.content_width, + self.content_height, + self.absolute_content_left, + self.absolute_content_top, ) def _reset(self): diff --git a/src/travertino/node.py b/src/travertino/node.py index 84dd162..73ab0c9 100644 --- a/src/travertino/node.py +++ b/src/travertino/node.py @@ -1,4 +1,3 @@ - class Node: def __init__(self, style, applicator=None, children=None): self.applicator = applicator @@ -26,7 +25,7 @@ def root(self): @property def parent(self): - """ The parent of this node. + """The parent of this node. Returns: The parent of this node. Returns None if this node is the root node. @@ -35,7 +34,7 @@ def parent(self): @property def children(self): - """ The children of this node. + """The children of this node. This *always* returns a list, even if the node is a leaf and cannot have children. @@ -65,7 +64,7 @@ def add(self, child): ValueError: If this node is a leaf, and cannot have children. """ if self._children is None: - raise ValueError('Cannot add children') + raise ValueError("Cannot add children") self._children.append(child) child._parent = self @@ -81,7 +80,7 @@ def insert(self, index, child): ValueError: If this node is a leaf, and cannot have children. """ if self._children is None: - raise ValueError('Cannot insert child') + raise ValueError("Cannot insert child") self._children.insert(index, child) child._parent = self @@ -96,7 +95,7 @@ def remove(self, child): ValueError: If this node is a leaf, and cannot have children. """ if self._children is None: - raise ValueError('Cannot remove children') + raise ValueError("Cannot remove children") self._children.remove(child) child._parent = None diff --git a/src/travertino/size.py b/src/travertino/size.py index 726ee14..1104386 100644 --- a/src/travertino/size.py +++ b/src/travertino/size.py @@ -1,10 +1,11 @@ class at_least: "An annotation to wrap around a value to describe that it is a minimum bound" + def __init__(self, value): self.value = value def __repr__(self): - return f'at least {self.value}' + return f"at least {self.value}" def __eq__(self, other): try: @@ -20,6 +21,7 @@ class BaseIntrinsicSize: height: The height of the node. ratio: The height between height and width. width = height * ratio """ + def __init__(self, width=None, height=None, ratio=None, layout=None): self._layout = layout self._width = width @@ -28,7 +30,7 @@ def __init__(self, width=None, height=None, ratio=None, layout=None): self._ratio = None def __repr__(self): - return f'({self.width}, {self.height})' + return f"({self.width}, {self.height})" @property def width(self): diff --git a/tests/colors/test_color_constructor.py b/tests/colors/test_color_constructor.py index 99d4903..e07548a 100644 --- a/tests/colors/test_color_constructor.py +++ b/tests/colors/test_color_constructor.py @@ -38,7 +38,9 @@ def test_hsl_hash(self): def test_hsla_hash(self): self.assertEqual(hash(hsla(10, 0.2, 0.3, 0.5)), hash(hsla(10, 0.2, 0.3, 0.5))) self.assertEqual(hash(hsla(10, 0.2, 0.3, 1.0)), hash(hsl(10, 0.2, 0.3))) - self.assertNotEqual(hash(hsla(10, 0.3, 0.2, 0.5)), hash(hsla(10, 0.2, 0.3, 0.5))) + self.assertNotEqual( + hash(hsla(10, 0.3, 0.2, 0.5)), hash(hsla(10, 0.2, 0.3, 0.5)) + ) self.assertNotEqual(hash(hsla(10, 0, 0, 0.5)), hash(rgba(10, 0, 0, 0.5))) @@ -50,27 +52,27 @@ def test_hsl_blacks(self): self.assertEqualColor(hsl(360, 0.0, 0.0), rgb(0x00, 0x00, 0x00)) def test_hsl_whites(self): - self.assertEqualColor(hsl(0, 0.0, 1.0), rgb(0xff, 0xff, 0xff)) - self.assertEqualColor(hsl(60, 0.0, 1.0), rgb(0xff, 0xff, 0xff)) - self.assertEqualColor(hsl(180, 0.0, 1.0), rgb(0xff, 0xff, 0xff)) - self.assertEqualColor(hsl(240, 0.0, 1.0), rgb(0xff, 0xff, 0xff)) - self.assertEqualColor(hsl(360, 0.0, 1.0), rgb(0xff, 0xff, 0xff)) + self.assertEqualColor(hsl(0, 0.0, 1.0), rgb(0xFF, 0xFF, 0xFF)) + self.assertEqualColor(hsl(60, 0.0, 1.0), rgb(0xFF, 0xFF, 0xFF)) + self.assertEqualColor(hsl(180, 0.0, 1.0), rgb(0xFF, 0xFF, 0xFF)) + self.assertEqualColor(hsl(240, 0.0, 1.0), rgb(0xFF, 0xFF, 0xFF)) + self.assertEqualColor(hsl(360, 0.0, 1.0), rgb(0xFF, 0xFF, 0xFF)) def test_hsl_grays(self): self.assertEqualColor(hsl(0, 0.0, 0.2), rgb(0x33, 0x33, 0x33)) self.assertEqualColor(hsl(0, 0.0, 0.4), rgb(0x66, 0x66, 0x66)) self.assertEqualColor(hsl(0, 0.0, 0.5), rgb(0x80, 0x80, 0x80)) self.assertEqualColor(hsl(0, 0.0, 0.6), rgb(0x99, 0x99, 0x99)) - self.assertEqualColor(hsl(0, 0.0, 0.8), rgb(0xcc, 0xcc, 0xcc)) + self.assertEqualColor(hsl(0, 0.0, 0.8), rgb(0xCC, 0xCC, 0xCC)) def test_hsl_primaries(self): - self.assertEqualColor(hsl(0, 1.0, 0.5), rgb(0xff, 0x00, 0x00)) - self.assertEqualColor(hsl(60, 1.0, 0.5), rgb(0xff, 0xff, 0x00)) - self.assertEqualColor(hsl(120, 1.0, 0.5), rgb(0x00, 0xff, 0x00)) - self.assertEqualColor(hsl(180, 1.0, 0.5), rgb(0x00, 0xff, 0xff)) - self.assertEqualColor(hsl(240, 1.0, 0.5), rgb(0x00, 0x00, 0xff)) - self.assertEqualColor(hsl(300, 1.0, 0.5), rgb(0xff, 0x00, 0xff)) - self.assertEqualColor(hsl(360, 1.0, 0.5), rgb(0xff, 0x00, 0x00)) + self.assertEqualColor(hsl(0, 1.0, 0.5), rgb(0xFF, 0x00, 0x00)) + self.assertEqualColor(hsl(60, 1.0, 0.5), rgb(0xFF, 0xFF, 0x00)) + self.assertEqualColor(hsl(120, 1.0, 0.5), rgb(0x00, 0xFF, 0x00)) + self.assertEqualColor(hsl(180, 1.0, 0.5), rgb(0x00, 0xFF, 0xFF)) + self.assertEqualColor(hsl(240, 1.0, 0.5), rgb(0x00, 0x00, 0xFF)) + self.assertEqualColor(hsl(300, 1.0, 0.5), rgb(0xFF, 0x00, 0xFF)) + self.assertEqualColor(hsl(360, 1.0, 0.5), rgb(0xFF, 0x00, 0x00)) def test_hsl_muted(self): self.assertEqualColor(hsl(0, 0.25, 0.25), rgb(0x50, 0x30, 0x30)) @@ -81,21 +83,21 @@ def test_hsl_muted(self): self.assertEqualColor(hsl(300, 0.25, 0.25), rgb(0x50, 0x30, 0x50)) self.assertEqualColor(hsl(360, 0.25, 0.25), rgb(0x50, 0x30, 0x30)) - self.assertEqualColor(hsl(0, 0.25, 0.75), rgb(0xcf, 0xaf, 0xaf)) - self.assertEqualColor(hsl(60, 0.25, 0.75), rgb(0xcf, 0xcf, 0xaf)) - self.assertEqualColor(hsl(120, 0.25, 0.75), rgb(0xaf, 0xcf, 0xaf)) - self.assertEqualColor(hsl(180, 0.25, 0.75), rgb(0xaf, 0xcf, 0xcf)) - self.assertEqualColor(hsl(240, 0.25, 0.75), rgb(0xaf, 0xaf, 0xcf)) - self.assertEqualColor(hsl(300, 0.25, 0.75), rgb(0xcf, 0xaf, 0xcf)) - self.assertEqualColor(hsl(360, 0.25, 0.75), rgb(0xcf, 0xaf, 0xaf)) - - self.assertEqualColor(hsl(0, 0.75, 0.75), rgb(0xef, 0x8f, 0x8f)) - self.assertEqualColor(hsl(60, 0.75, 0.75), rgb(0xef, 0xef, 0x8f)) - self.assertEqualColor(hsl(120, 0.75, 0.75), rgb(0x8f, 0xef, 0x8f)) - self.assertEqualColor(hsl(180, 0.75, 0.75), rgb(0x8f, 0xef, 0xef)) - self.assertEqualColor(hsl(240, 0.75, 0.75), rgb(0x8f, 0x8f, 0xef)) - self.assertEqualColor(hsl(300, 0.75, 0.75), rgb(0xef, 0x8f, 0xef)) - self.assertEqualColor(hsl(360, 0.75, 0.75), rgb(0xef, 0x8f, 0x8f)) + self.assertEqualColor(hsl(0, 0.25, 0.75), rgb(0xCF, 0xAF, 0xAF)) + self.assertEqualColor(hsl(60, 0.25, 0.75), rgb(0xCF, 0xCF, 0xAF)) + self.assertEqualColor(hsl(120, 0.25, 0.75), rgb(0xAF, 0xCF, 0xAF)) + self.assertEqualColor(hsl(180, 0.25, 0.75), rgb(0xAF, 0xCF, 0xCF)) + self.assertEqualColor(hsl(240, 0.25, 0.75), rgb(0xAF, 0xAF, 0xCF)) + self.assertEqualColor(hsl(300, 0.25, 0.75), rgb(0xCF, 0xAF, 0xCF)) + self.assertEqualColor(hsl(360, 0.25, 0.75), rgb(0xCF, 0xAF, 0xAF)) + + self.assertEqualColor(hsl(0, 0.75, 0.75), rgb(0xEF, 0x8F, 0x8F)) + self.assertEqualColor(hsl(60, 0.75, 0.75), rgb(0xEF, 0xEF, 0x8F)) + self.assertEqualColor(hsl(120, 0.75, 0.75), rgb(0x8F, 0xEF, 0x8F)) + self.assertEqualColor(hsl(180, 0.75, 0.75), rgb(0x8F, 0xEF, 0xEF)) + self.assertEqualColor(hsl(240, 0.75, 0.75), rgb(0x8F, 0x8F, 0xEF)) + self.assertEqualColor(hsl(300, 0.75, 0.75), rgb(0xEF, 0x8F, 0xEF)) + self.assertEqualColor(hsl(360, 0.75, 0.75), rgb(0xEF, 0x8F, 0x8F)) self.assertEqualColor(hsl(0, 0.75, 0.25), rgb(0x70, 0x10, 0x10)) self.assertEqualColor(hsl(60, 0.75, 0.25), rgb(0x70, 0x70, 0x10)) @@ -107,11 +109,11 @@ def test_hsl_muted(self): def test_hsl_alpha(self): self.assertEqualColor(hsla(60, 0.0, 0.0, 0.3), rgba(0x00, 0x00, 0x00, 0.3)) - self.assertEqualColor(hsla(60, 0.0, 1.0, 0.3), rgba(0xff, 0xff, 0xff, 0.3)) - self.assertEqualColor(hsla(60, 1.0, 0.5, 0.3), rgba(0xff, 0xff, 0x00, 0.3)) + self.assertEqualColor(hsla(60, 0.0, 1.0, 0.3), rgba(0xFF, 0xFF, 0xFF, 0.3)) + self.assertEqualColor(hsla(60, 1.0, 0.5, 0.3), rgba(0xFF, 0xFF, 0x00, 0.3)) self.assertEqualColor(hsla(60, 0.25, 0.25, 0.3), rgba(0x50, 0x50, 0x30, 0.3)) - self.assertEqualColor(hsla(60, 0.25, 0.75, 0.3), rgba(0xcf, 0xcf, 0xaf, 0.3)) - self.assertEqualColor(hsla(60, 0.75, 0.75, 0.3), rgba(0xef, 0xef, 0x8f, 0.3)) + self.assertEqualColor(hsla(60, 0.25, 0.75, 0.3), rgba(0xCF, 0xCF, 0xAF, 0.3)) + self.assertEqualColor(hsla(60, 0.75, 0.75, 0.3), rgba(0xEF, 0xEF, 0x8F, 0.3)) self.assertEqualColor(hsla(60, 0.75, 0.25, 0.3), rgba(0x70, 0x70, 0x10, 0.3)) @@ -137,116 +139,116 @@ def test_noop(self): self.assertEqualHSL(hsl(1, 0.2, 0.3), hsl(1, 0.2, 0.3)) def test_rgb(self): - self.assertEqualColor('rgb(1,2,3)', rgb(1, 2, 3)) - self.assertEqualColor('rgb(1, 2, 3)', rgb(1, 2, 3)) - self.assertEqualColor('rgb( 1 , 2 , 3)', rgb(1, 2, 3)) + self.assertEqualColor("rgb(1,2,3)", rgb(1, 2, 3)) + self.assertEqualColor("rgb(1, 2, 3)", rgb(1, 2, 3)) + self.assertEqualColor("rgb( 1 , 2 , 3)", rgb(1, 2, 3)) - self.assertEqualColor('#123', rgb(0x11, 0x22, 0x33)) - self.assertEqualColor('#112233', rgb(0x11, 0x22, 0x33)) - self.assertEqualColor('#abc', rgb(0xaa, 0xbb, 0xcc)) - self.assertEqualColor('#ABC', rgb(0xaa, 0xbb, 0xcc)) - self.assertEqualColor('#abcdef', rgb(0xab, 0xcd, 0xef)) - self.assertEqualColor('#ABCDEF', rgb(0xab, 0xcd, 0xef)) + self.assertEqualColor("#123", rgb(0x11, 0x22, 0x33)) + self.assertEqualColor("#112233", rgb(0x11, 0x22, 0x33)) + self.assertEqualColor("#abc", rgb(0xAA, 0xBB, 0xCC)) + self.assertEqualColor("#ABC", rgb(0xAA, 0xBB, 0xCC)) + self.assertEqualColor("#abcdef", rgb(0xAB, 0xCD, 0xEF)) + self.assertEqualColor("#ABCDEF", rgb(0xAB, 0xCD, 0xEF)) with self.assertRaises(ValueError): - color('rgb(10, 20)') + color("rgb(10, 20)") with self.assertRaises(ValueError): - color('rgb(a, 10, 20)') + color("rgb(a, 10, 20)") with self.assertRaises(ValueError): - color('rgb(10, b, 20)') + color("rgb(10, b, 20)") with self.assertRaises(ValueError): - color('rgb(10, 20, c)') + color("rgb(10, 20, c)") with self.assertRaises(ValueError): - color('rgb(10, 20, 30, 0.5)') + color("rgb(10, 20, 30, 0.5)") def test_rgba(self): - self.assertEqualColor('rgba(1,2,3,0.5)', rgba(1, 2, 3, 0.5)) - self.assertEqualColor('rgba(1, 2, 3, 0.5)', rgba(1, 2, 3, 0.5)) - self.assertEqualColor('rgba( 1 , 2 , 3 , 0.5)', rgba(1, 2, 3, 0.5)) + self.assertEqualColor("rgba(1,2,3,0.5)", rgba(1, 2, 3, 0.5)) + self.assertEqualColor("rgba(1, 2, 3, 0.5)", rgba(1, 2, 3, 0.5)) + self.assertEqualColor("rgba( 1 , 2 , 3 , 0.5)", rgba(1, 2, 3, 0.5)) - self.assertEqualColor('#1234', rgba(0x11, 0x22, 0x33, 0.2666)) - self.assertEqualColor('#11223344', rgba(0x11, 0x22, 0x33, 0.2666)) - self.assertEqualColor('#abcd', rgba(0xaa, 0xbb, 0xcc, 0.8666)) - self.assertEqualColor('#ABCD', rgba(0xaa, 0xbb, 0xcc, 0.8666)) - self.assertEqualColor('#abcdefba', rgba(0xab, 0xcd, 0xef, 0.7294)) - self.assertEqualColor('#ABCDEFBA', rgba(0xab, 0xcd, 0xef, 0.7294)) + self.assertEqualColor("#1234", rgba(0x11, 0x22, 0x33, 0.2666)) + self.assertEqualColor("#11223344", rgba(0x11, 0x22, 0x33, 0.2666)) + self.assertEqualColor("#abcd", rgba(0xAA, 0xBB, 0xCC, 0.8666)) + self.assertEqualColor("#ABCD", rgba(0xAA, 0xBB, 0xCC, 0.8666)) + self.assertEqualColor("#abcdefba", rgba(0xAB, 0xCD, 0xEF, 0.7294)) + self.assertEqualColor("#ABCDEFBA", rgba(0xAB, 0xCD, 0xEF, 0.7294)) with self.assertRaises(ValueError): - color('rgba(10, 20, 30)') + color("rgba(10, 20, 30)") with self.assertRaises(ValueError): - color('rgba(a, 10, 20, 0.5)') + color("rgba(a, 10, 20, 0.5)") with self.assertRaises(ValueError): - color('rgba(10, b, 20, 0.5)') + color("rgba(10, b, 20, 0.5)") with self.assertRaises(ValueError): - color('rgba(10, 20, c, 0.5)') + color("rgba(10, 20, c, 0.5)") with self.assertRaises(ValueError): - color('rgba(10, 20, 30, c)') + color("rgba(10, 20, 30, c)") with self.assertRaises(ValueError): - color('rgba(10, 20, 30, 0.5, 5)') + color("rgba(10, 20, 30, 0.5, 5)") def test_hsl(self): - self.assertEqualHSL('hsl(1,20%,30%)', hsl(1, 0.2, 0.3)) - self.assertEqualHSL('hsl(1, 20%, 30%)', hsl(1, 0.2, 0.3)) - self.assertEqualHSL('hsl( 1, 20% , 30%)', hsl(1, 0.2, 0.3)) + self.assertEqualHSL("hsl(1,20%,30%)", hsl(1, 0.2, 0.3)) + self.assertEqualHSL("hsl(1, 20%, 30%)", hsl(1, 0.2, 0.3)) + self.assertEqualHSL("hsl( 1, 20% , 30%)", hsl(1, 0.2, 0.3)) with self.assertRaises(ValueError): - color('hsl(1, 20%)') + color("hsl(1, 20%)") with self.assertRaises(ValueError): - color('hsl(a, 20%, 30%)') + color("hsl(a, 20%, 30%)") with self.assertRaises(ValueError): - color('hsl(1, a, 30%)') + color("hsl(1, a, 30%)") with self.assertRaises(ValueError): - color('hsl(1, 20%, a)') + color("hsl(1, 20%, a)") with self.assertRaises(ValueError): - color('hsl(1, 20%, 30%, 0.5)') + color("hsl(1, 20%, 30%, 0.5)") def test_hsla(self): - self.assertEqualHSL('hsla(1,20%,30%,0.5)', hsla(1, 0.2, 0.3, 0.5)) - self.assertEqualHSL('hsla(1, 20%, 30%, 0.5)', hsla(1, 0.2, 0.3, 0.5)) - self.assertEqualHSL('hsla( 1, 20% , 30% , 0.5)', hsla(1, 0.2, 0.3, 0.5)) + self.assertEqualHSL("hsla(1,20%,30%,0.5)", hsla(1, 0.2, 0.3, 0.5)) + self.assertEqualHSL("hsla(1, 20%, 30%, 0.5)", hsla(1, 0.2, 0.3, 0.5)) + self.assertEqualHSL("hsla( 1, 20% , 30% , 0.5)", hsla(1, 0.2, 0.3, 0.5)) with self.assertRaises(ValueError): - color('hsla(1, 20%, 30%)') + color("hsla(1, 20%, 30%)") with self.assertRaises(ValueError): - color('hsla(a, 20%, 30%, 0.5)') + color("hsla(a, 20%, 30%, 0.5)") with self.assertRaises(ValueError): - color('hsla(1, a, 30%, 0.5)') + color("hsla(1, a, 30%, 0.5)") with self.assertRaises(ValueError): - color('hsla(1, 20%, a, 0.5)') + color("hsla(1, 20%, a, 0.5)") with self.assertRaises(ValueError): - color('hsla(1, 20%, 30%, a)') + color("hsla(1, 20%, 30%, a)") with self.assertRaises(ValueError): - color('hsla(1, 20%, 30%, 0.5, 5)') + color("hsla(1, 20%, 30%, 0.5, 5)") def test_named_color(self): - self.assertEqualColor('Red', rgb(0xFF, 0, 0)) - self.assertEqualColor('RED', rgb(0xFF, 0, 0)) - self.assertEqualColor('red', rgb(0xFF, 0, 0)) - self.assertEqualColor('rEd', rgb(0xFF, 0, 0)) + self.assertEqualColor("Red", rgb(0xFF, 0, 0)) + self.assertEqualColor("RED", rgb(0xFF, 0, 0)) + self.assertEqualColor("red", rgb(0xFF, 0, 0)) + self.assertEqualColor("rEd", rgb(0xFF, 0, 0)) - self.assertEqualColor('CornflowerBlue', rgb(0x64, 0x95, 0xED)) - self.assertEqualColor('cornflowerblue', rgb(0x64, 0x95, 0xED)) - self.assertEqualColor('CORNFLOWERBLUE', rgb(0x64, 0x95, 0xED)) - self.assertEqualColor('Cornflowerblue', rgb(0x64, 0x95, 0xED)) - self.assertEqualColor('CoRnFlOwErBlUe', rgb(0x64, 0x95, 0xED)) + self.assertEqualColor("CornflowerBlue", rgb(0x64, 0x95, 0xED)) + self.assertEqualColor("cornflowerblue", rgb(0x64, 0x95, 0xED)) + self.assertEqualColor("CORNFLOWERBLUE", rgb(0x64, 0x95, 0xED)) + self.assertEqualColor("Cornflowerblue", rgb(0x64, 0x95, 0xED)) + self.assertEqualColor("CoRnFlOwErBlUe", rgb(0x64, 0x95, 0xED)) with self.assertRaises(ValueError): - color('not a color') + color("not a color") diff --git a/tests/colors/test_color_exceptions.py b/tests/colors/test_color_exceptions.py index 228cce4..ea124f4 100644 --- a/tests/colors/test_color_exceptions.py +++ b/tests/colors/test_color_exceptions.py @@ -1,10 +1,9 @@ from unittest import TestCase -from travertino.colors import rgb, rgba, hsl, hsla +from travertino.colors import hsl, hsla, rgb, rgba class RGBColorExceptionTests(TestCase): - def test_negative_red(self): self.assertRaisesRegex( ValueError, "^red value should be between 0-255. Got -1$", rgb, -1, 120, 10 @@ -17,7 +16,7 @@ def test_too_big_red(self): rgb, 256, 120, - 10 + 10, ) def test_negative_green(self): @@ -27,7 +26,7 @@ def test_negative_green(self): rgb, 120, -1, - 10 + 10, ) def test_too_big_green(self): @@ -37,7 +36,7 @@ def test_too_big_green(self): rgb, 120, 256, - 10 + 10, ) def test_negative_blue(self): @@ -47,7 +46,7 @@ def test_negative_blue(self): rgb, 120, 10, - -1 + -1, ) def test_too_big_blue(self): @@ -57,12 +56,11 @@ def test_too_big_blue(self): rgb, 120, 10, - 256 + 256, ) class RGBAColorExceptionTests(TestCase): - def test_negative_red(self): self.assertRaisesRegex( ValueError, @@ -71,7 +69,7 @@ def test_negative_red(self): -1, 120, 10, - 0.5 + 0.5, ) def test_too_big_red(self): @@ -82,7 +80,7 @@ def test_too_big_red(self): 256, 120, 10, - 0.5 + 0.5, ) def test_negative_green(self): @@ -93,7 +91,7 @@ def test_negative_green(self): 120, -1, 10, - 0.5 + 0.5, ) def test_too_big_green(self): @@ -104,7 +102,7 @@ def test_too_big_green(self): 120, 256, 10, - 0.5 + 0.5, ) def test_negative_blue(self): @@ -115,7 +113,7 @@ def test_negative_blue(self): 120, 10, -1, - 0.5 + 0.5, ) def test_too_big_blue(self): @@ -126,7 +124,7 @@ def test_too_big_blue(self): 120, 10, 256, - 0.5 + 0.5, ) def test_negative_alpha(self): @@ -137,7 +135,7 @@ def test_negative_alpha(self): 120, 10, 60, - -0.5 + -0.5, ) def test_too_big_alpha(self): @@ -148,15 +146,19 @@ def test_too_big_alpha(self): 120, 10, 60, - 1.1 + 1.1, ) class HSLColorExceptionTests(TestCase): - def test_negative_hue(self): self.assertRaisesRegex( - ValueError, "^hue value should be between 0-360. Got -1$", hsl, -1, 0.5, 0.8 + ValueError, + "^hue value should be between 0-360. Got -1$", + hsl, + -1, + 0.5, + 0.8, ) def test_too_big_hue(self): @@ -166,7 +168,7 @@ def test_too_big_hue(self): hsl, 361, 0.5, - 0.8 + 0.8, ) def test_negative_saturation(self): @@ -176,7 +178,7 @@ def test_negative_saturation(self): hsl, 120, -0.1, - 0.8 + 0.8, ) def test_too_big_saturation(self): @@ -186,7 +188,7 @@ def test_too_big_saturation(self): hsl, 120, 1.1, - 0.8 + 0.8, ) def test_negative_lightness(self): @@ -196,7 +198,7 @@ def test_negative_lightness(self): hsl, 120, 0.8, - -0.1 + -0.1, ) def test_too_big_lightness(self): @@ -206,12 +208,11 @@ def test_too_big_lightness(self): hsl, 120, 0.8, - 1.1 + 1.1, ) class HSLAColorExceptionTests(TestCase): - def test_negative_hue(self): self.assertRaisesRegex( ValueError, @@ -220,7 +221,7 @@ def test_negative_hue(self): -1, 0.5, 0.8, - 0.5 + 0.5, ) def test_too_big_hue(self): @@ -231,7 +232,7 @@ def test_too_big_hue(self): 361, 0.5, 0.8, - 0.5 + 0.5, ) def test_negative_saturation(self): @@ -242,7 +243,7 @@ def test_negative_saturation(self): 120, -0.1, 0.8, - 0.5 + 0.5, ) def test_too_big_saturation(self): @@ -253,7 +254,7 @@ def test_too_big_saturation(self): 120, 1.1, 0.8, - 0.5 + 0.5, ) def test_negative_lightness(self): @@ -264,7 +265,7 @@ def test_negative_lightness(self): 120, 0.8, -0.1, - 0.5 + 0.5, ) def test_too_big_lightness(self): @@ -275,7 +276,7 @@ def test_too_big_lightness(self): 120, 0.8, 1.1, - 0.5 + 0.5, ) def test_negative_alpha(self): @@ -297,5 +298,5 @@ def test_too_big_alpha(self): 120, 0.8, 0.5, - 1.1 + 1.1, ) diff --git a/tests/test_choices.py b/tests/test_choices.py index eb49142..ad4d80b 100644 --- a/tests/test_choices.py +++ b/tests/test_choices.py @@ -2,22 +2,23 @@ from unittest.mock import Mock from travertino.colors import NAMED_COLOR, rgb -from travertino.constants import TOP, GOLDENROD, REBECCAPURPLE -from travertino.declaration import Choices, BaseStyle +from travertino.constants import GOLDENROD, REBECCAPURPLE, TOP +from travertino.declaration import BaseStyle, Choices class PropertyChoiceTests(TestCase): def assert_property(self, obj, value, check_mock=True): self.assertEqual(obj.prop, value) if check_mock: - obj.apply.assert_called_once_with('prop', value) + obj.apply.assert_called_once_with("prop", value) obj.apply.reset_mock() def test_none(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices(None), initial=None) + + MyObject.validated_property("prop", choices=Choices(None), initial=None) obj = MyObject() self.assertIsNone(obj.prop) @@ -32,38 +33,41 @@ def __init__(self): obj.prop = REBECCAPURPLE with self.assertRaises(ValueError): - obj.prop = '#112233' + obj.prop = "#112233" with self.assertRaises(ValueError): - obj.prop = 'a' + obj.prop = "a" with self.assertRaises(ValueError): - obj.prop = 'b' + obj.prop = "b" obj.prop = None self.assert_property(obj, None, check_mock=False) - obj.prop = 'none' + obj.prop = "none" self.assert_property(obj, None, check_mock=False) # Check the error message try: - obj.prop = 'invalid' - self.fail('Should raise ValueError') + obj.prop = "invalid" + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), - "Invalid value 'invalid' for property 'prop'; Valid values are: none" + "Invalid value 'invalid' for property 'prop'; Valid values are: none", ) def test_allow_string(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices(string=True), initial='start') + + MyObject.validated_property( + "prop", choices=Choices(string=True), initial="start" + ) obj = MyObject() - self.assertEqual(obj.prop, 'start') + self.assertEqual(obj.prop, "start") with self.assertRaises(ValueError): obj.prop = 10 @@ -72,38 +76,39 @@ def __init__(self): obj.prop = 3.14159 obj.prop = REBECCAPURPLE - self.assert_property(obj, 'rebeccapurple') + self.assert_property(obj, "rebeccapurple") - obj.prop = '#112233' - self.assert_property(obj, '#112233') + obj.prop = "#112233" + self.assert_property(obj, "#112233") - obj.prop = 'a' - self.assert_property(obj, 'a') + obj.prop = "a" + self.assert_property(obj, "a") - obj.prop = 'b' - self.assert_property(obj, 'b') + obj.prop = "b" + self.assert_property(obj, "b") with self.assertRaises(ValueError): obj.prop = None - obj.prop = 'none' - self.assert_property(obj, 'none') + obj.prop = "none" + self.assert_property(obj, "none") # Check the error message try: obj.prop = 99 - self.fail('Should raise ValueError') + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), - "Invalid value '99' for property 'prop'; Valid values are: " + "Invalid value '99' for property 'prop'; Valid values are: ", ) def test_allow_integer(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices(integer=True), initial=0) + + MyObject.validated_property("prop", choices=Choices(integer=True), initial=0) obj = MyObject() self.assertEqual(obj.prop, 0) @@ -120,35 +125,36 @@ def __init__(self): obj.prop = REBECCAPURPLE with self.assertRaises(ValueError): - obj.prop = '#112233' + obj.prop = "#112233" with self.assertRaises(ValueError): - obj.prop = 'a' + obj.prop = "a" with self.assertRaises(ValueError): - obj.prop = 'b' + obj.prop = "b" with self.assertRaises(ValueError): obj.prop = None with self.assertRaises(ValueError): - obj.prop = 'none' + obj.prop = "none" # Check the error message try: - obj.prop = 'invalid' - self.fail('Should raise ValueError') + obj.prop = "invalid" + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), - "Invalid value 'invalid' for property 'prop'; Valid values are: " + "Invalid value 'invalid' for property 'prop'; Valid values are: ", ) def test_allow_number(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices(number=True), initial=0) + + MyObject.validated_property("prop", choices=Choices(number=True), initial=0) obj = MyObject() self.assertEqual(obj.prop, 0) @@ -163,35 +169,38 @@ def __init__(self): obj.prop = REBECCAPURPLE with self.assertRaises(ValueError): - obj.prop = '#112233' + obj.prop = "#112233" with self.assertRaises(ValueError): - obj.prop = 'a' + obj.prop = "a" with self.assertRaises(ValueError): - obj.prop = 'b' + obj.prop = "b" with self.assertRaises(ValueError): obj.prop = None with self.assertRaises(ValueError): - obj.prop = 'none' + obj.prop = "none" # Check the error message try: - obj.prop = 'invalid' - self.fail('Should raise ValueError') + obj.prop = "invalid" + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), - "Invalid value 'invalid' for property 'prop'; Valid values are: " + "Invalid value 'invalid' for property 'prop'; Valid values are: ", ) def test_allow_color(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices(color=True), initial='goldenrod') + + MyObject.validated_property( + "prop", choices=Choices(color=True), initial="goldenrod" + ) obj = MyObject() self.assertEqual(obj.prop, NAMED_COLOR[GOLDENROD]) @@ -205,39 +214,42 @@ def __init__(self): obj.prop = REBECCAPURPLE self.assert_property(obj, NAMED_COLOR[REBECCAPURPLE]) - obj.prop = '#112233' + obj.prop = "#112233" self.assert_property(obj, rgb(0x11, 0x22, 0x33)) with self.assertRaises(ValueError): - obj.prop = 'a' + obj.prop = "a" with self.assertRaises(ValueError): - obj.prop = 'b' + obj.prop = "b" with self.assertRaises(ValueError): obj.prop = None with self.assertRaises(ValueError): - obj.prop = 'none' + obj.prop = "none" # Check the error message try: - obj.prop = 'invalid' - self.fail('Should raise ValueError') + obj.prop = "invalid" + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), - "Invalid value 'invalid' for property 'prop'; Valid values are: " + "Invalid value 'invalid' for property 'prop'; Valid values are: ", ) def test_values(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices('a', 'b', None), initial='a') + + MyObject.validated_property( + "prop", choices=Choices("a", "b", None), initial="a" + ) obj = MyObject() - self.assertEqual(obj.prop, 'a') + self.assertEqual(obj.prop, "a") with self.assertRaises(ValueError): obj.prop = 10 @@ -249,41 +261,39 @@ def __init__(self): obj.prop = REBECCAPURPLE with self.assertRaises(ValueError): - obj.prop = '#112233' + obj.prop = "#112233" obj.prop = None self.assert_property(obj, None) - obj.prop = 'a' - self.assert_property(obj, 'a') + obj.prop = "a" + self.assert_property(obj, "a") - obj.prop = 'none' + obj.prop = "none" self.assert_property(obj, None) - obj.prop = 'b' - self.assert_property(obj, 'b') + obj.prop = "b" + self.assert_property(obj, "b") # Check the error message try: - obj.prop = 'invalid' - self.fail('Should raise ValueError') + obj.prop = "invalid" + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), - "Invalid value 'invalid' for property 'prop'; Valid values are: a, b, none" + "Invalid value 'invalid' for property 'prop'; Valid values are: a, b, none", ) def test_multiple_choices(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() + MyObject.validated_property( - 'prop', - choices=Choices( - 'a', 'b', None, - number=True, color=True - ), - initial=None + "prop", + choices=Choices("a", "b", None, number=True, color=True), + initial=None, ) obj = MyObject() @@ -297,37 +307,38 @@ def __init__(self): obj.prop = REBECCAPURPLE self.assert_property(obj, NAMED_COLOR[REBECCAPURPLE]) - obj.prop = '#112233' + obj.prop = "#112233" self.assert_property(obj, rgb(0x11, 0x22, 0x33)) obj.prop = None self.assert_property(obj, None) - obj.prop = 'a' - self.assert_property(obj, 'a') + obj.prop = "a" + self.assert_property(obj, "a") - obj.prop = 'none' + obj.prop = "none" self.assert_property(obj, None) - obj.prop = 'b' - self.assert_property(obj, 'b') + obj.prop = "b" + self.assert_property(obj, "b") # Check the error message try: - obj.prop = 'invalid' - self.fail('Should raise ValueError') + obj.prop = "invalid" + self.fail("Should raise ValueError") except ValueError as v: self.assertEqual( str(v), "Invalid value 'invalid' for property 'prop'; " - "Valid values are: a, b, none, , " + "Valid values are: a, b, none, , ", ) def test_string_symbol(self): class MyObject(BaseStyle): def __init__(self): self.apply = Mock() - MyObject.validated_property('prop', choices=Choices(TOP, None), initial=None) + + MyObject.validated_property("prop", choices=Choices(TOP, None), initial=None) obj = MyObject() @@ -335,7 +346,7 @@ def __init__(self): # We can't just use the string directly, though - that would # get optimized by the compiler. So we create a string and # transform it into the value we want. - val = 'TOP' + val = "TOP" obj.prop = val.lower() # Both equality and instance checking should work. diff --git a/tests/test_declaration.py b/tests/test_declaration.py index d9bfaaa..26b7d92 100644 --- a/tests/test_declaration.py +++ b/tests/test_declaration.py @@ -3,10 +3,9 @@ from travertino.declaration import BaseStyle, Choices - -VALUE1 = 'value1' -VALUE2 = 'value2' -VALUE3 = 'value3' +VALUE1 = "value1" +VALUE2 = "value2" +VALUE3 = "value3" VALUE_CHOICES = Choices(VALUE1, VALUE2, VALUE3, None, integer=True) DEFAULT_VALUE_CHOICES = Choices(VALUE1, VALUE2, VALUE3, integer=True, default=True) @@ -18,20 +17,20 @@ def __init__(self, **kwargs): # Some properties with explicit initial values -Style.validated_property('explicit_const', choices=VALUE_CHOICES, initial=VALUE1) -Style.validated_property('explicit_value', choices=VALUE_CHOICES, initial=0) -Style.validated_property('explicit_none', choices=VALUE_CHOICES, initial=None) +Style.validated_property("explicit_const", choices=VALUE_CHOICES, initial=VALUE1) +Style.validated_property("explicit_value", choices=VALUE_CHOICES, initial=0) +Style.validated_property("explicit_none", choices=VALUE_CHOICES, initial=None) # A property with an implicit default value. # This usually means the default is platform specific. -Style.validated_property('implicit', choices=DEFAULT_VALUE_CHOICES) +Style.validated_property("implicit", choices=DEFAULT_VALUE_CHOICES) # A set of directional properties -Style.validated_property('thing_top', choices=VALUE_CHOICES, initial=0) -Style.validated_property('thing_right', choices=VALUE_CHOICES, initial=0) -Style.validated_property('thing_bottom', choices=VALUE_CHOICES, initial=0) -Style.validated_property('thing_left', choices=VALUE_CHOICES, initial=0) -Style.directional_property('thing%s') +Style.validated_property("thing_top", choices=VALUE_CHOICES, initial=0) +Style.validated_property("thing_right", choices=VALUE_CHOICES, initial=0) +Style.validated_property("thing_bottom", choices=VALUE_CHOICES, initial=0) +Style.validated_property("thing_left", choices=VALUE_CHOICES, initial=0) +Style.directional_property("thing%s") class TestNode: @@ -48,7 +47,10 @@ def test_invalid_style(self): # Define a style that has an invalid initial value on a validated property class BadStyle(BaseStyle): pass - BadStyle.validated_property('value', choices=VALUE_CHOICES, initial='something') + + BadStyle.validated_property( + "value", choices=VALUE_CHOICES, initial="something" + ) def test_create_and_copy(self): style = Style(explicit_const=VALUE2, implicit=VALUE3) @@ -62,16 +64,19 @@ def test_reapply(self): node = TestNode(style=Style(explicit_const=VALUE2, implicit=VALUE3)) node.style.reapply() - node.style.apply.assert_has_calls([ - call('explicit_const', VALUE2), - call('explicit_value', 0), - call('explicit_none', None), - call('implicit', VALUE3), - call('thing_left', 0), - call('thing_top', 0), - call('thing_right', 0), - call('thing_bottom', 0), - ], any_order=True) + node.style.apply.assert_has_calls( + [ + call("explicit_const", VALUE2), + call("explicit_value", 0), + call("explicit_none", None), + call("implicit", VALUE3), + call("thing_left", 0), + call("thing_top", 0), + call("thing_right", 0), + call("thing_bottom", 0), + ], + any_order=True, + ) def test_property_with_explicit_const(self): node = TestNode() @@ -84,7 +89,7 @@ def test_property_with_explicit_const(self): node.style.explicit_const = 10 self.assertEqual(node.style.explicit_const, 10) - node.style.apply.assert_called_once_with('explicit_const', 10) + node.style.apply.assert_called_once_with("explicit_const", 10) # Clear the applicator mock node.style.apply.reset_mock() @@ -99,7 +104,7 @@ def test_property_with_explicit_const(self): # A dirty notification is set. node.style.explicit_const = 20 self.assertEqual(node.style.explicit_const, 20) - node.style.apply.assert_called_once_with('explicit_const', 20) + node.style.apply.assert_called_once_with("explicit_const", 20) # Clear the applicator mock node.style.apply.reset_mock() @@ -107,7 +112,7 @@ def test_property_with_explicit_const(self): # Clear the property del node.style.explicit_const self.assertIs(node.style.explicit_const, VALUE1) - node.style.apply.assert_called_once_with('explicit_const', VALUE1) + node.style.apply.assert_called_once_with("explicit_const", VALUE1) # Clear the applicator mock node.style.apply.reset_mock() @@ -130,7 +135,7 @@ def test_property_with_explicit_value(self): node.style.explicit_value = 10 self.assertEqual(node.style.explicit_value, 10) - node.style.apply.assert_called_once_with('explicit_value', 10) + node.style.apply.assert_called_once_with("explicit_value", 10) # Clear the applicator mock node.style.apply.reset_mock() @@ -145,7 +150,7 @@ def test_property_with_explicit_value(self): # A dirty notification is set. node.style.explicit_value = 20 self.assertEqual(node.style.explicit_value, 20) - node.style.apply.assert_called_once_with('explicit_value', 20) + node.style.apply.assert_called_once_with("explicit_value", 20) # Clear the applicator mock node.style.apply.reset_mock() @@ -153,7 +158,7 @@ def test_property_with_explicit_value(self): # Clear the property del node.style.explicit_value self.assertEqual(node.style.explicit_value, 0) - node.style.apply.assert_called_once_with('explicit_value', 0) + node.style.apply.assert_called_once_with("explicit_value", 0) def test_property_with_explicit_none(self): node = TestNode() @@ -166,7 +171,7 @@ def test_property_with_explicit_none(self): node.style.explicit_none = 10 self.assertEqual(node.style.explicit_none, 10) - node.style.apply.assert_called_once_with('explicit_none', 10) + node.style.apply.assert_called_once_with("explicit_none", 10) # Clear the applicator mock node.style.apply.reset_mock() @@ -181,7 +186,7 @@ def test_property_with_explicit_none(self): # A dirty notification is set. node.style.explicit_none = 20 self.assertEqual(node.style.explicit_none, 20) - node.style.apply.assert_called_once_with('explicit_none', 20) + node.style.apply.assert_called_once_with("explicit_none", 20) # Clear the applicator mock node.style.apply.reset_mock() @@ -189,7 +194,7 @@ def test_property_with_explicit_none(self): # Clear the property del node.style.explicit_none self.assertIsNone(node.style.explicit_none) - node.style.apply.assert_called_once_with('explicit_none', None) + node.style.apply.assert_called_once_with("explicit_none", None) def test_property_with_implicit_default(self): node = TestNode() @@ -202,7 +207,7 @@ def test_property_with_implicit_default(self): node.style.implicit = 10 self.assertEqual(node.style.implicit, 10) - node.style.apply.assert_called_once_with('implicit', 10) + node.style.apply.assert_called_once_with("implicit", 10) # Clear the applicator mock node.style.apply.reset_mock() @@ -217,7 +222,7 @@ def test_property_with_implicit_default(self): # A dirty notification is set. node.style.implicit = 20 self.assertEqual(node.style.implicit, 20) - node.style.apply.assert_called_once_with('implicit', 20) + node.style.apply.assert_called_once_with("implicit", 20) # Clear the applicator mock node.style.apply.reset_mock() @@ -225,7 +230,7 @@ def test_property_with_implicit_default(self): # Clear the property del node.style.implicit self.assertIsNone(node.style.implicit) - node.style.apply.assert_called_once_with('implicit', None) + node.style.apply.assert_called_once_with("implicit", None) def test_directional_property(self): node = TestNode() @@ -246,7 +251,7 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 0) self.assertEqual(node.style.thing_bottom, 0) self.assertEqual(node.style.thing_left, 0) - node.style.apply.assert_called_once_with('thing_top', 10) + node.style.apply.assert_called_once_with("thing_top", 10) # Clear the applicator mock node.style.apply.reset_mock() @@ -259,11 +264,13 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 10) self.assertEqual(node.style.thing_bottom, 10) self.assertEqual(node.style.thing_left, 10) - node.style.apply.assert_has_calls([ - call('thing_right', 10), - call('thing_bottom', 10), - call('thing_left', 10), - ]) + node.style.apply.assert_has_calls( + [ + call("thing_right", 10), + call("thing_bottom", 10), + call("thing_left", 10), + ] + ) # Clear the applicator mock node.style.apply.reset_mock() @@ -276,12 +283,14 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 30) self.assertEqual(node.style.thing_bottom, 30) self.assertEqual(node.style.thing_left, 30) - node.style.apply.assert_has_calls([ - call('thing_top', 30), - call('thing_right', 30), - call('thing_bottom', 30), - call('thing_left', 30), - ]) + node.style.apply.assert_has_calls( + [ + call("thing_top", 30), + call("thing_right", 30), + call("thing_bottom", 30), + call("thing_left", 30), + ] + ) # Clear the applicator mock node.style.apply.reset_mock() @@ -294,12 +303,14 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 20) self.assertEqual(node.style.thing_bottom, 10) self.assertEqual(node.style.thing_left, 20) - node.style.apply.assert_has_calls([ - call('thing_top', 10), - call('thing_right', 20), - call('thing_bottom', 10), - call('thing_left', 20), - ]) + node.style.apply.assert_has_calls( + [ + call("thing_top", 10), + call("thing_right", 20), + call("thing_bottom", 10), + call("thing_left", 20), + ] + ) # Clear the applicator mock node.style.apply.reset_mock() @@ -312,7 +323,7 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 20) self.assertEqual(node.style.thing_bottom, 30) self.assertEqual(node.style.thing_left, 20) - node.style.apply.assert_called_once_with('thing_bottom', 30) + node.style.apply.assert_called_once_with("thing_bottom", 30) # Clear the applicator mock node.style.apply.reset_mock() @@ -325,7 +336,7 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 20) self.assertEqual(node.style.thing_bottom, 30) self.assertEqual(node.style.thing_left, 40) - node.style.apply.assert_called_once_with('thing_left', 40) + node.style.apply.assert_called_once_with("thing_left", 40) # Set a value directly with an invalid number of values with self.assertRaises(ValueError): @@ -345,7 +356,7 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 20) self.assertEqual(node.style.thing_bottom, 30) self.assertEqual(node.style.thing_left, 40) - node.style.apply.assert_called_once_with('thing_top', 0) + node.style.apply.assert_called_once_with("thing_top", 0) # Restore the top thing node.style.thing_top = 10 @@ -361,11 +372,13 @@ def test_directional_property(self): self.assertEqual(node.style.thing_right, 0) self.assertEqual(node.style.thing_bottom, 0) self.assertEqual(node.style.thing_left, 0) - node.style.apply.assert_has_calls([ - call('thing_right', 0), - call('thing_bottom', 0), - call('thing_left', 0), - ]) + node.style.apply.assert_has_calls( + [ + call("thing_right", 0), + call("thing_bottom", 0), + call("thing_left", 0), + ] + ) def test_set_multiple_properties(self): node = TestNode() @@ -378,10 +391,10 @@ def test_set_multiple_properties(self): self.assertEqual(node.style.explicit_value, 20) node.style.apply.assert_has_calls( [ - call('explicit_value', 20), - call('explicit_none', 10), + call("explicit_value", 20), + call("explicit_none", 10), ], - any_order=True + any_order=True, ) # Set a different pair of properties @@ -392,10 +405,10 @@ def test_set_multiple_properties(self): self.assertEqual(node.style.explicit_none, 10) node.style.apply.assert_has_calls( [ - call('explicit_const', VALUE2), - call('explicit_value', 30), + call("explicit_const", VALUE2), + call("explicit_value", 30), ], - any_order=True + any_order=True, ) # Clear the applicator mock @@ -423,7 +436,7 @@ def test_str(self): "thing-bottom: 50; " "thing-left: 60; " "thing-right: 40; " - "thing-top: 30" + "thing-top: 30", ) def test_dict(self): @@ -438,42 +451,51 @@ def test_dict(self): self.assertEqual( node.style.keys(), - {'explicit_const', 'explicit_value', 'thing_bottom', 'thing_left', 'thing_right', 'thing_top'} + { + "explicit_const", + "explicit_value", + "thing_bottom", + "thing_left", + "thing_right", + "thing_top", + }, ) self.assertEqual( sorted(node.style.items()), - sorted([ - ('explicit_const', 'value2'), - ('explicit_value', 20), - ('thing_bottom', 50), - ('thing_left', 60), - ('thing_right', 40), - ('thing_top', 30), - ]) + sorted( + [ + ("explicit_const", "value2"), + ("explicit_value", 20), + ("thing_bottom", 50), + ("thing_left", 60), + ("thing_right", 40), + ("thing_top", 30), + ] + ), ) # A property can be set, retrieved and cleared using the attribute name - node.style['thing-bottom'] = 10 - self.assertEqual(node.style['thing-bottom'], 10) - del node.style['thing-bottom'] - self.assertEqual(node.style['thing-bottom'], 0) + node.style["thing-bottom"] = 10 + self.assertEqual(node.style["thing-bottom"], 10) + del node.style["thing-bottom"] + self.assertEqual(node.style["thing-bottom"], 0) # A property can be set, retrieved and cleared using the Python attribute name - node.style['thing_bottom'] = 10 - self.assertEqual(node.style['thing_bottom'], 10) - del node.style['thing_bottom'] - self.assertEqual(node.style['thing_bottom'], 0) + node.style["thing_bottom"] = 10 + self.assertEqual(node.style["thing_bottom"], 10) + del node.style["thing_bottom"] + self.assertEqual(node.style["thing_bottom"], 0) # Clearing a valid property isn't an error - del node.style['thing_bottom'] - self.assertEqual(node.style['thing_bottom'], 0) + del node.style["thing_bottom"] + self.assertEqual(node.style["thing_bottom"], 0) # Non-existent properties raise KeyError with self.assertRaises(KeyError): - node.style['no-such-property'] = 'no-such-value' + node.style["no-such-property"] = "no-such-value" with self.assertRaises(KeyError): - node.style['no-such-property'] + node.style["no-such-property"] with self.assertRaises(KeyError): - del node.style['no-such-property'] + del node.style["no-such-property"] diff --git a/tests/test_font.py b/tests/test_font.py index b230748..d063ca0 100644 --- a/tests/test_font.py +++ b/tests/test_font.py @@ -21,240 +21,233 @@ def assertFont(self, font, family, size, style, variant, weight): def test_equality(self): self.assertEqual( - Font('Comic Sans', '12 pt'), - Font('Comic Sans', 12, NORMAL, NORMAL, NORMAL) + Font("Comic Sans", "12 pt"), Font("Comic Sans", 12, NORMAL, NORMAL, NORMAL) ) def test_hash(self): self.assertEqual( - hash(Font('Comic Sans', 12)), - hash(Font('Comic Sans', 12)), + hash(Font("Comic Sans", 12)), + hash(Font("Comic Sans", 12)), ) self.assertNotEqual( - hash(Font('Comic Sans', 12, weight=BOLD)), - hash(Font('Comic Sans', 12)), + hash(Font("Comic Sans", 12, weight=BOLD)), + hash(Font("Comic Sans", 12)), ) def test_repr(self): - self.assertEqual( - repr(Font('Comic Sans', 12)), - '' - ) + self.assertEqual(repr(Font("Comic Sans", 12)), "") self.assertEqual( - repr(Font('Comic Sans', 12, style=ITALIC)), - '' + repr(Font("Comic Sans", 12, style=ITALIC)), "" ) self.assertEqual( - repr(Font('Comic Sans', 12, style=ITALIC, variant=SMALL_CAPS)), - '' + repr(Font("Comic Sans", 12, style=ITALIC, variant=SMALL_CAPS)), + "", ) self.assertEqual( - repr(Font('Comic Sans', 12, style=ITALIC, variant=SMALL_CAPS, weight=BOLD)), - '' + repr(Font("Comic Sans", 12, style=ITALIC, variant=SMALL_CAPS, weight=BOLD)), + "", ) self.assertEqual( - repr(Font('Comic Sans', 12, variant=SMALL_CAPS, weight=BOLD)), - '' + repr(Font("Comic Sans", 12, variant=SMALL_CAPS, weight=BOLD)), + "", ) self.assertEqual( - repr(Font('Comic Sans', 12, weight=BOLD)), - '' + repr(Font("Comic Sans", 12, weight=BOLD)), "" ) self.assertEqual( - repr(Font('Comic Sans', 12, style=ITALIC, weight=BOLD)), - '' + repr(Font("Comic Sans", 12, style=ITALIC, weight=BOLD)), + "", ) # Check system default size handling self.assertEqual( - repr(Font('Comic Sans', SYSTEM_DEFAULT_FONT_SIZE)), - '' + repr(Font("Comic Sans", SYSTEM_DEFAULT_FONT_SIZE)), + "", ) self.assertEqual( - repr(Font('Comic Sans', SYSTEM_DEFAULT_FONT_SIZE, style=ITALIC)), - '' + repr(Font("Comic Sans", SYSTEM_DEFAULT_FONT_SIZE, style=ITALIC)), + "", ) def test_simple_construction(self): # Simplest case self.assertFont( - Font('Comic Sans', 12), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", 12), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) # String size self.assertFont( - Font('Comic Sans', '12'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", "12"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) # String size with 'pt' self.assertFont( - Font('Comic Sans', '12pt'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", "12pt"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) self.assertFont( - Font('Comic Sans', '12 pt'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", "12 pt"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) with self.assertRaises(ValueError): - Font('Comic Sans', '12 quatloos'), + Font("Comic Sans", "12 quatloos"), def test_family(self): self.assertFont( - Font('Comic Sans', 12), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", 12), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) - self.assertFont( - Font('Wingdings', 12), - 'Wingdings', 12, NORMAL, NORMAL, NORMAL - ) + self.assertFont(Font("Wingdings", 12), "Wingdings", 12, NORMAL, NORMAL, NORMAL) self.assertFont( - Font("'Comic Sans'", 12), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("'Comic Sans'", 12), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) self.assertFont( - Font('"Comic Sans"', 12), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font('"Comic Sans"', 12), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) def test_style(self): self.assertFont( - Font('Comic Sans', 12, style=ITALIC), - 'Comic Sans', 12, ITALIC, NORMAL, NORMAL + Font("Comic Sans", 12, style=ITALIC), + "Comic Sans", + 12, + ITALIC, + NORMAL, + NORMAL, ) self.assertFont( - Font('Comic Sans', 12, style='italic'), - 'Comic Sans', 12, ITALIC, NORMAL, NORMAL + Font("Comic Sans", 12, style="italic"), + "Comic Sans", + 12, + ITALIC, + NORMAL, + NORMAL, ) self.assertFont( - Font('Comic Sans', 12, style=OBLIQUE), - 'Comic Sans', 12, OBLIQUE, NORMAL, NORMAL + Font("Comic Sans", 12, style=OBLIQUE), + "Comic Sans", + 12, + OBLIQUE, + NORMAL, + NORMAL, ) self.assertFont( - Font('Comic Sans', 12, style='oblique'), - 'Comic Sans', 12, OBLIQUE, NORMAL, NORMAL + Font("Comic Sans", 12, style="oblique"), + "Comic Sans", + 12, + OBLIQUE, + NORMAL, + NORMAL, ) self.assertFont( - Font('Comic Sans', 12, style='something else'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", 12, style="something else"), + "Comic Sans", + 12, + NORMAL, + NORMAL, + NORMAL, ) def test_make_normal_style(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.normal_style(), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12) + self.assertFont(f.normal_style(), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) - f = Font('Comic Sans', 12, style=ITALIC) - self.assertFont( - f.normal_style(), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12, style=ITALIC) + self.assertFont(f.normal_style(), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) def test_make_italic(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.italic(), - 'Comic Sans', 12, ITALIC, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12) + self.assertFont(f.italic(), "Comic Sans", 12, ITALIC, NORMAL, NORMAL) def test_make_oblique(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.oblique(), - 'Comic Sans', 12, OBLIQUE, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12) + self.assertFont(f.oblique(), "Comic Sans", 12, OBLIQUE, NORMAL, NORMAL) def test_variant(self): self.assertFont( - Font('Comic Sans', 12, variant=SMALL_CAPS), - 'Comic Sans', 12, NORMAL, SMALL_CAPS, NORMAL + Font("Comic Sans", 12, variant=SMALL_CAPS), + "Comic Sans", + 12, + NORMAL, + SMALL_CAPS, + NORMAL, ) self.assertFont( - Font('Comic Sans', 12, variant='small-caps'), - 'Comic Sans', 12, NORMAL, SMALL_CAPS, NORMAL + Font("Comic Sans", 12, variant="small-caps"), + "Comic Sans", + 12, + NORMAL, + SMALL_CAPS, + NORMAL, ) self.assertFont( - Font('Comic Sans', 12, variant='something else'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", 12, variant="something else"), + "Comic Sans", + 12, + NORMAL, + NORMAL, + NORMAL, ) def test_make_normal_variant(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.normal_variant(), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12) + self.assertFont(f.normal_variant(), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) - f = Font('Comic Sans', 12, variant=SMALL_CAPS) - self.assertFont( - f.normal_variant(), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12, variant=SMALL_CAPS) + self.assertFont(f.normal_variant(), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) def test_make_small_caps(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.small_caps(), - 'Comic Sans', 12, NORMAL, SMALL_CAPS, NORMAL - ) + f = Font("Comic Sans", 12) + self.assertFont(f.small_caps(), "Comic Sans", 12, NORMAL, SMALL_CAPS, NORMAL) def test_weight(self): self.assertFont( - Font('Comic Sans', 12, weight=BOLD), - 'Comic Sans', 12, NORMAL, NORMAL, BOLD + Font("Comic Sans", 12, weight=BOLD), "Comic Sans", 12, NORMAL, NORMAL, BOLD ) self.assertFont( - Font('Comic Sans', 12, weight='bold'), - 'Comic Sans', 12, NORMAL, NORMAL, BOLD + Font("Comic Sans", 12, weight="bold"), + "Comic Sans", + 12, + NORMAL, + NORMAL, + BOLD, ) self.assertFont( - Font('Comic Sans', 12, weight='something else'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + Font("Comic Sans", 12, weight="something else"), + "Comic Sans", + 12, + NORMAL, + NORMAL, + NORMAL, ) def test_make_normal_weight(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.normal_weight(), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12) + self.assertFont(f.normal_weight(), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) - f = Font('Comic Sans', 12, weight=BOLD) - self.assertFont( - f.normal_weight(), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + f = Font("Comic Sans", 12, weight=BOLD) + self.assertFont(f.normal_weight(), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) def test_make_bold(self): - f = Font('Comic Sans', 12) - self.assertFont( - f.bold(), - 'Comic Sans', 12, NORMAL, NORMAL, BOLD - ) + f = Font("Comic Sans", 12) + self.assertFont(f.bold(), "Comic Sans", 12, NORMAL, NORMAL, BOLD) class ParseFontTests(TestCase): @@ -266,7 +259,7 @@ def assertFont(self, font, family, size, style, variant, weight): self.assertEqual(font.weight, weight) def test_font_instance(self): - f = Font('Comic Sans', 12) + f = Font("Comic Sans", 12) parsed = font(f) @@ -275,130 +268,144 @@ def test_font_instance(self): def test_successful_combinations(self): self.assertFont( - font('12pt Comic Sans'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + font("12pt Comic Sans"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) self.assertFont( - font('italic 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, NORMAL, NORMAL + font("italic 12pt Comic Sans"), "Comic Sans", 12, ITALIC, NORMAL, NORMAL ) self.assertFont( - font('italic small-caps 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, SMALL_CAPS, NORMAL + font("italic small-caps 12pt Comic Sans"), + "Comic Sans", + 12, + ITALIC, + SMALL_CAPS, + NORMAL, ) self.assertFont( - font('italic small-caps bold 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, SMALL_CAPS, BOLD + font("italic small-caps bold 12pt Comic Sans"), + "Comic Sans", + 12, + ITALIC, + SMALL_CAPS, + BOLD, ) self.assertFont( - font('small-caps bold 12pt Comic Sans'), - 'Comic Sans', 12, NORMAL, SMALL_CAPS, BOLD + font("small-caps bold 12pt Comic Sans"), + "Comic Sans", + 12, + NORMAL, + SMALL_CAPS, + BOLD, ) self.assertFont( - font('italic bold 12 pt Comic Sans'), - 'Comic Sans', 12, ITALIC, NORMAL, BOLD + font("italic bold 12 pt Comic Sans"), "Comic Sans", 12, ITALIC, NORMAL, BOLD ) self.assertFont( - font('bold 12 pt Comic Sans'), - 'Comic Sans', 12, NORMAL, NORMAL, BOLD + font("bold 12 pt Comic Sans"), "Comic Sans", 12, NORMAL, NORMAL, BOLD ) def test_font_sizes(self): self.assertFont( - font('12pt Comic Sans'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + font("12pt Comic Sans"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) self.assertFont( - font('12 pt Comic Sans'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + font("12 pt Comic Sans"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) - self.assertFont( - font('12 Comic Sans'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL - ) + self.assertFont(font("12 Comic Sans"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL) with self.assertRaises(ValueError): - font('12quatloo Comic Sans') + font("12quatloo Comic Sans") def test_font_family(self): self.assertFont( - font("12pt 'Comic Sans'"), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + font("12pt 'Comic Sans'"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) self.assertFont( - font('12pt "Comic Sans"'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + font('12pt "Comic Sans"'), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) def test_normal(self): self.assertFont( - font('normal 12pt Comic Sans'), - 'Comic Sans', 12, NORMAL, NORMAL, NORMAL + font("normal 12pt Comic Sans"), "Comic Sans", 12, NORMAL, NORMAL, NORMAL ) self.assertFont( - font('italic normal 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, NORMAL, NORMAL + font("italic normal 12pt Comic Sans"), + "Comic Sans", + 12, + ITALIC, + NORMAL, + NORMAL, ) self.assertFont( - font('italic small-caps normal 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, SMALL_CAPS, NORMAL + font("italic small-caps normal 12pt Comic Sans"), + "Comic Sans", + 12, + ITALIC, + SMALL_CAPS, + NORMAL, ) def test_style(self): self.assertFont( - font('italic 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, NORMAL, NORMAL + font("italic 12pt Comic Sans"), "Comic Sans", 12, ITALIC, NORMAL, NORMAL ) self.assertFont( - font('oblique 12pt Comic Sans'), - 'Comic Sans', 12, OBLIQUE, NORMAL, NORMAL + font("oblique 12pt Comic Sans"), "Comic Sans", 12, OBLIQUE, NORMAL, NORMAL ) with self.assertRaises(ValueError): - font('wiggly small-caps bold 12pt Comic Sans') + font("wiggly small-caps bold 12pt Comic Sans") def test_variant(self): self.assertFont( - font('italic small-caps 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, SMALL_CAPS, NORMAL + font("italic small-caps 12pt Comic Sans"), + "Comic Sans", + 12, + ITALIC, + SMALL_CAPS, + NORMAL, ) with self.assertRaises(ValueError): - font('italic wiggly bold 12pt Comic Sans') + font("italic wiggly bold 12pt Comic Sans") def test_weight(self): self.assertFont( - font('italic small-caps bold 12pt Comic Sans'), - 'Comic Sans', 12, ITALIC, SMALL_CAPS, BOLD + font("italic small-caps bold 12pt Comic Sans"), + "Comic Sans", + 12, + ITALIC, + SMALL_CAPS, + BOLD, ) with self.assertRaises(ValueError): - font('italic small-caps wiggly 12pt Comic Sans') + font("italic small-caps wiggly 12pt Comic Sans") def test_duplicates(self): with self.assertRaises(ValueError): - font('oblique italic 12pt Comic Sans') + font("oblique italic 12pt Comic Sans") with self.assertRaises(ValueError): - font('italic small-caps oblique 12pt Comic Sans') + font("italic small-caps oblique 12pt Comic Sans") with self.assertRaises(ValueError): - font('italic small-caps bold small-caps 12pt Comic Sans') + font("italic small-caps bold small-caps 12pt Comic Sans") with self.assertRaises(ValueError): - font('bold bold 12pt Comic Sans') + font("bold bold 12pt Comic Sans") def test_invaid(self): with self.assertRaises(ValueError): diff --git a/tests/test_layout.py b/tests/test_layout.py index c6e1530..1aa6847 100644 --- a/tests/test_layout.py +++ b/tests/test_layout.py @@ -44,7 +44,9 @@ def setUp(self): self.grandchild1_1.layout.content_height = 16 self.grandchild1_2 = Node(style=Style()) - self.child1 = Node(style=Style(), children=[self.grandchild1_1, self.grandchild1_2]) + self.child1 = Node( + style=Style(), children=[self.grandchild1_1, self.grandchild1_2] + ) self.child1.layout.content_width = 10 self.child1.layout.content_height = 16 self.child2 = Node(style=Style(), children=[]) @@ -55,16 +57,16 @@ def setUp(self): def assertLayout(self, box, expected): actual = { - 'origin': (box._origin_left, box._origin_top), - 'size': (box.width, box.height), - 'content': (box.content_width, box.content_height), - 'relative': ( + "origin": (box._origin_left, box._origin_top), + "size": (box.width, box.height), + "content": (box.content_width, box.content_height), + "relative": ( box.content_top, box.content_right, box.content_bottom, box.content_left, ), - 'absolute': ( + "absolute": ( box.absolute_content_top, box.absolute_content_right, box.absolute_content_bottom, @@ -83,12 +85,12 @@ def test_initial(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (10, 16), - 'content': (10, 16), - 'relative': (0, 0, 0, 0), - 'absolute': (0, 10, 16, 0), - } + "origin": (0, 0), + "size": (10, 16), + "content": (10, 16), + "relative": (0, 0, 0, 0), + "absolute": (0, 10, 16, 0), + }, ) def test_set_content_top(self): @@ -97,12 +99,12 @@ def test_set_content_top(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (10, 21), - 'content': (10, 16), - 'relative': (5, 0, 0, 0), - 'absolute': (5, 10, 21, 0), - } + "origin": (0, 0), + "size": (10, 21), + "content": (10, 16), + "relative": (5, 0, 0, 0), + "absolute": (5, 10, 21, 0), + }, ) # Set the top to a new value @@ -111,12 +113,12 @@ def test_set_content_top(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (10, 23), - 'content': (10, 16), - 'relative': (7, 0, 0, 0), - 'absolute': (7, 10, 23, 0), - } + "origin": (0, 0), + "size": (10, 23), + "content": (10, 16), + "relative": (7, 0, 0, 0), + "absolute": (7, 10, 23, 0), + }, ) def test_set_content_left(self): @@ -125,12 +127,12 @@ def test_set_content_left(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (15, 16), - 'content': (10, 16), - 'relative': (0, 0, 0, 5), - 'absolute': (0, 15, 16, 5), - } + "origin": (0, 0), + "size": (15, 16), + "content": (10, 16), + "relative": (0, 0, 0, 5), + "absolute": (0, 15, 16, 5), + }, ) # Set the left to a new value @@ -139,12 +141,12 @@ def test_set_content_left(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (17, 16), - 'content': (10, 16), - 'relative': (0, 0, 0, 7), - 'absolute': (0, 17, 16, 7), - } + "origin": (0, 0), + "size": (17, 16), + "content": (10, 16), + "relative": (0, 0, 0, 7), + "absolute": (0, 17, 16, 7), + }, ) def test_set_content_width(self): @@ -153,12 +155,12 @@ def test_set_content_width(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (5, 16), - 'content': (5, 16), - 'relative': (0, 0, 0, 0), - 'absolute': (0, 5, 16, 0), - } + "origin": (0, 0), + "size": (5, 16), + "content": (5, 16), + "relative": (0, 0, 0, 0), + "absolute": (0, 5, 16, 0), + }, ) # Set the width to a new value @@ -167,12 +169,12 @@ def test_set_content_width(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (7, 16), - 'content': (7, 16), - 'relative': (0, 0, 0, 0), - 'absolute': (0, 7, 16, 0), - } + "origin": (0, 0), + "size": (7, 16), + "content": (7, 16), + "relative": (0, 0, 0, 0), + "absolute": (0, 7, 16, 0), + }, ) def test_set_content_height(self): @@ -181,12 +183,12 @@ def test_set_content_height(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (10, 5), - 'content': (10, 5), - 'relative': (0, 0, 0, 0), - 'absolute': (0, 10, 5, 0), - } + "origin": (0, 0), + "size": (10, 5), + "content": (10, 5), + "relative": (0, 0, 0, 0), + "absolute": (0, 10, 5, 0), + }, ) # Set the height to a new value @@ -195,12 +197,12 @@ def test_set_content_height(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (10, 7), - 'content': (10, 7), - 'relative': (0, 0, 0, 0), - 'absolute': (0, 10, 7, 0), - } + "origin": (0, 0), + "size": (10, 7), + "content": (10, 7), + "relative": (0, 0, 0, 0), + "absolute": (0, 10, 7, 0), + }, ) def test_descendent_offsets(self): @@ -216,34 +218,34 @@ def test_descendent_offsets(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (18, 23), - 'content': (10, 16), - 'relative': (7, 0, 0, 8), - 'absolute': (7, 18, 23, 8), - } + "origin": (0, 0), + "size": (18, 23), + "content": (10, 16), + "relative": (7, 0, 0, 8), + "absolute": (7, 18, 23, 8), + }, ) self.assertLayout( self.child1.layout, { - 'origin': (8, 7), - 'size': (20, 25), - 'content': (10, 16), - 'relative': (9, 0, 0, 10), - 'absolute': (16, 28, 32, 18), - } + "origin": (8, 7), + "size": (20, 25), + "content": (10, 16), + "relative": (9, 0, 0, 10), + "absolute": (16, 28, 32, 18), + }, ) self.assertLayout( self.grandchild1_1.layout, { - 'origin': (18, 16), - 'size': (22, 27), - 'content': (10, 16), - 'relative': (11, 0, 0, 12), - 'absolute': (27, 40, 43, 30), - } + "origin": (18, 16), + "size": (22, 27), + "content": (10, 16), + "relative": (11, 0, 0, 12), + "absolute": (27, 40, 43, 30), + }, ) # Modify the grandchild position @@ -254,34 +256,34 @@ def test_descendent_offsets(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (18, 23), - 'content': (10, 16), - 'relative': (7, 0, 0, 8), - 'absolute': (7, 18, 23, 8), - } + "origin": (0, 0), + "size": (18, 23), + "content": (10, 16), + "relative": (7, 0, 0, 8), + "absolute": (7, 18, 23, 8), + }, ) self.assertLayout( self.child1.layout, { - 'origin': (8, 7), - 'size': (20, 25), - 'content': (10, 16), - 'relative': (9, 0, 0, 10), - 'absolute': (16, 28, 32, 18), - } + "origin": (8, 7), + "size": (20, 25), + "content": (10, 16), + "relative": (9, 0, 0, 10), + "absolute": (16, 28, 32, 18), + }, ) self.assertLayout( self.grandchild1_1.layout, { - 'origin': (18, 16), - 'size': (24, 29), - 'content': (10, 16), - 'relative': (13, 0, 0, 14), - 'absolute': (29, 42, 45, 32), - } + "origin": (18, 16), + "size": (24, 29), + "content": (10, 16), + "relative": (13, 0, 0, 14), + "absolute": (29, 42, 45, 32), + }, ) # Modify the child position @@ -292,34 +294,34 @@ def test_descendent_offsets(self): self.assertLayout( self.node.layout, { - 'origin': (0, 0), - 'size': (18, 23), - 'content': (10, 16), - 'relative': (7, 0, 0, 8), - 'absolute': (7, 18, 23, 8), - } + "origin": (0, 0), + "size": (18, 23), + "content": (10, 16), + "relative": (7, 0, 0, 8), + "absolute": (7, 18, 23, 8), + }, ) self.assertLayout( self.child1.layout, { - 'origin': (8, 7), - 'size': (26, 31), - 'content': (10, 16), - 'relative': (15, 0, 0, 16), - 'absolute': (22, 34, 38, 24), - } + "origin": (8, 7), + "size": (26, 31), + "content": (10, 16), + "relative": (15, 0, 0, 16), + "absolute": (22, 34, 38, 24), + }, ) self.assertLayout( self.grandchild1_1.layout, { - 'origin': (24, 22), - 'size': (24, 29), - 'content': (10, 16), - 'relative': (13, 0, 0, 14), - 'absolute': (35, 48, 51, 38), - } + "origin": (24, 22), + "size": (24, 29), + "content": (10, 16), + "relative": (13, 0, 0, 14), + "absolute": (35, 48, 51, 38), + }, ) def test_absolute_equalities(self): @@ -334,18 +336,22 @@ def test_absolute_equalities(self): self.assertEqual( self.node.layout.absolute_content_left + self.node.layout.content_width, - self.node.layout.absolute_content_right + self.node.layout.absolute_content_right, ) self.assertEqual( self.node.layout.absolute_content_top + self.node.layout.content_height, - self.node.layout.absolute_content_bottom + self.node.layout.absolute_content_bottom, ) self.assertEqual( - self.node.layout.content_left + self.node.layout.content_width + self.node.layout.content_right, - self.node.layout.width + self.node.layout.content_left + + self.node.layout.content_width + + self.node.layout.content_right, + self.node.layout.width, ) self.assertEqual( - self.node.layout.content_top + self.node.layout.content_height + self.node.layout.content_bottom, - self.node.layout.height + self.node.layout.content_top + + self.node.layout.content_height + + self.node.layout.content_bottom, + self.node.layout.height, ) diff --git a/tests/test_node.py b/tests/test_node.py index 8d16e6c..4823924 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -98,6 +98,7 @@ def test_create_node(self): def test_refresh(self): "The layout can be refreshed, and the applicator invoked" + # Define an applicator that tracks the node being rendered and it's size class Applicator: def __init__(self, node): @@ -105,14 +106,18 @@ def __init__(self, node): self.node = node def set_bounds(self): - self.tasks.append((self.node, self.node.layout.content_width, self.node.layout.content_height)) + self.tasks.append( + ( + self.node, + self.node.layout.content_width, + self.node.layout.content_height, + ) + ) class TestNode(Node): def __init__(self, style, children=None): super().__init__( - style=style, - applicator=Applicator(self), - children=children + style=style, applicator=Applicator(self), children=children ) # Define a simple 2 level tree of nodes. diff --git a/tests/test_size.py b/tests/test_size.py index e18880f..23c7762 100644 --- a/tests/test_size.py +++ b/tests/test_size.py @@ -22,12 +22,12 @@ def setUp(self): self.assertSize(self.size, (1, 2, 0.1)) def test_at_least_repr(self): - self.assertEqual(repr(at_least(10)), 'at least 10') + self.assertEqual(repr(at_least(10)), "at least 10") def test_size_repr(self): - self.assertEqual(repr(self.size), '(1, 2)') + self.assertEqual(repr(self.size), "(1, 2)") self.size.width = at_least(10) - self.assertEqual(repr(self.size), '(at least 10, 2)') + self.assertEqual(repr(self.size), "(at least 10, 2)") def test_set_width(self): self.size.width = 10 diff --git a/tox.ini b/tox.ini index bc12d4a..ee8643f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,22 +4,15 @@ # and then run "tox" from this directory. [tox] -envlist = flake8,towncrier-check,package,py{37,38,39,310,311,312} +envlist = towncrier-check,package,py{37,38,39,310,311,312} skip_missing_interpreters = true [testenv] -deps = - pytest - pytest-tldr +extras = + dev commands = pytest -vv -[testenv:flake8] -skip_install = True -deps = - flake8 -commands = flake8 {posargs} - [testenv:towncrier-check] skip_install = True deps = @@ -35,13 +28,15 @@ commands = towncrier {posargs} [testenv:package] +package_env = none +skip_install = True deps = check_manifest - wheel + build twine commands = check-manifest -v - python setup.py sdist bdist_wheel + python -m build --outdir dist/ . python -m twine check dist/* [testenv:publish]