# stretchable
[](https://pypi.org/project/stretchable/)
[](https://www.python.org)
[](https://github.com/mortencombat/stretchable/blob/main/LICENSE)
[](https://github.com/mortencombat/stretchable/issues)
[](https://github.com/mortencombat/stretchable/actions/workflows/build-publish.yml)
[](https://stretchable.readthedocs.io/en/latest/?badge=stable)
[](https://github.com/mortencombat/stretchable/actions/workflows/test.yml)
[](https://github.com/mortencombat/stretchable/actions/workflows/test.yml)
**stretchable** is a layout library for Python that enables context-agnostic layout operations using CSS Block, [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) and [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/). Possible uses include UI layouts, page layouts for reports, complex plotting layouts, etc.
It implements Python bindings for [Taffy](https://github.com/dioxuslabs/taffy), an implementation of **CSS Block**, **Flexbox** and **CSS Grid** layout algorithms written in [Rust](https://www.rust-lang.org/). It was originally based on [Stretch](https://vislyhq.github.io/stretch/) (hence the name), but has since migrated to use Taffy. It is multi-platform and there are distributions available for Windows, Linux and macOS.
## Getting Started
**stretchable** is a Python package [hosted on PyPI](https://pypi.org/project/stretchable/). It can be installed using [pip](https://pip.pypa.io/en/stable/):
```console
python -m pip install stretchable
```
Building a tree of nodes and calculating the layout is as simple as:
```python
from stretchable import Edge, Node
from stretchable.style import AUTO, PCT
# Build node tree
root = Node(
    margin=20,
    size=(500, 300),
).add(
    Node(border=5, size=(50 * PCT, AUTO)),
    Node(key="child", padding=10 * PCT, size=50 * PCT),
)
# Compute layout
root.compute_layout()
# Get the second of the child nodes
child_node = root.find("/child")
content_box = child_node.get_box(Edge.CONTENT)
print(content_box)
# Box(x=300.0, y=50.0, width=150.0, height=50.0)
```
For more information and details, see the [documentation](https://stretchable.readthedocs.io/).
## Contributing
Contributions are welcomed. Please open an issue to clarify/plan implementation details prior to starting the work.
### Building
Install Rust with [rustup](https://rustup.rs/) and use `maturin develop` for development and `maturin build [--release]` to build.
### Documentation
To build documentation use `make html` (in `docs/` folder) or, to use live reloading: `sphinx-autobuild docs/source docs/build/html`
NOTE: Sometimes, you may need to run `make clean html` (in `docs/` folder) to ensure that all changes are included in the built html.
### Testing
Install test dependencies and invoke `pytest`. Note that there are ~900 tests, the majority of which are run using Selenium with the Chrome WebDriver, and the complete test suite can take ~30 minutes to complete. Use `pytest --lf` to only run the last-failed tests.
To run basic tests and ensure compatibility with a specific Python version, use Docker and the provided `Dockerfile`: `docker build . [--build-arg pyver=3.11]` (defaults to lowest supported Python version, currently 3.8).
## License
This work is released under the MIT license. A copy of the license is provided in the [LICENSE](https://github.com/mortencombat/stretchable/blob/main/LICENSE) file.
            
         
        Raw data
        
            {
    "_id": null,
    "home_page": null,
    "name": "stretchable",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "flexbox, grid, block, stretch, css, layout",
    "author": "Kenneth Trelborg Vestergaard",
    "author_email": null,
    "download_url": null,
    "platform": null,
    "description": "# stretchable\r\n\r\n[](https://pypi.org/project/stretchable/)\r\n[](https://www.python.org)\r\n[](https://github.com/mortencombat/stretchable/blob/main/LICENSE)\r\n[](https://github.com/mortencombat/stretchable/issues)\r\n[](https://github.com/mortencombat/stretchable/actions/workflows/build-publish.yml)\r\n[](https://stretchable.readthedocs.io/en/latest/?badge=stable)\r\n[](https://github.com/mortencombat/stretchable/actions/workflows/test.yml)\r\n[](https://github.com/mortencombat/stretchable/actions/workflows/test.yml)\r\n\r\n**stretchable** is a layout library for Python that enables context-agnostic layout operations using CSS Block, [CSS Grid](https://css-tricks.com/snippets/css/complete-guide-grid/) and [Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/). Possible uses include UI layouts, page layouts for reports, complex plotting layouts, etc.\r\n\r\nIt implements Python bindings for [Taffy](https://github.com/dioxuslabs/taffy), an implementation of **CSS Block**, **Flexbox** and **CSS Grid** layout algorithms written in [Rust](https://www.rust-lang.org/). It was originally based on [Stretch](https://vislyhq.github.io/stretch/) (hence the name), but has since migrated to use Taffy. It is multi-platform and there are distributions available for Windows, Linux and macOS.\r\n\r\n## Getting Started\r\n\r\n**stretchable** is a Python package [hosted on PyPI](https://pypi.org/project/stretchable/). It can be installed using [pip](https://pip.pypa.io/en/stable/):\r\n\r\n```console\r\npython -m pip install stretchable\r\n```\r\n\r\nBuilding a tree of nodes and calculating the layout is as simple as:\r\n\r\n```python\r\nfrom stretchable import Edge, Node\r\nfrom stretchable.style import AUTO, PCT\r\n\r\n# Build node tree\r\nroot = Node(\r\n    margin=20,\r\n    size=(500, 300),\r\n).add(\r\n    Node(border=5, size=(50 * PCT, AUTO)),\r\n    Node(key=\"child\", padding=10 * PCT, size=50 * PCT),\r\n)\r\n\r\n# Compute layout\r\nroot.compute_layout()\r\n\r\n# Get the second of the child nodes\r\nchild_node = root.find(\"/child\")\r\ncontent_box = child_node.get_box(Edge.CONTENT)\r\nprint(content_box)\r\n# Box(x=300.0, y=50.0, width=150.0, height=50.0)\r\n\r\n```\r\n\r\nFor more information and details, see the [documentation](https://stretchable.readthedocs.io/).\r\n\r\n## Contributing\r\n\r\nContributions are welcomed. Please open an issue to clarify/plan implementation details prior to starting the work.\r\n\r\n### Building\r\n\r\nInstall Rust with [rustup](https://rustup.rs/) and use `maturin develop` for development and `maturin build [--release]` to build.\r\n\r\n### Documentation\r\n\r\nTo build documentation use `make html` (in `docs/` folder) or, to use live reloading: `sphinx-autobuild docs/source docs/build/html`\r\n\r\nNOTE: Sometimes, you may need to run `make clean html` (in `docs/` folder) to ensure that all changes are included in the built html.\r\n\r\n### Testing\r\n\r\nInstall test dependencies and invoke `pytest`. Note that there are ~900 tests, the majority of which are run using Selenium with the Chrome WebDriver, and the complete test suite can take ~30 minutes to complete. Use `pytest --lf` to only run the last-failed tests.\r\n\r\nTo run basic tests and ensure compatibility with a specific Python version, use Docker and the provided `Dockerfile`: `docker build . [--build-arg pyver=3.11]` (defaults to lowest supported Python version, currently 3.8).\r\n\r\n## License\r\n\r\nThis work is released under the MIT license. A copy of the license is provided in the [LICENSE](https://github.com/mortencombat/stretchable/blob/main/LICENSE) file.\r\n\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Layout library for Python (based on Taffy, a rust-powered implementation of CSS Grid/Flexbox)",
    "version": "1.1.8",
    "project_urls": {
        "Documentation": "https://stretchable.readthedocs.io/en/latest/",
        "Homepage": "https://stretchable.readthedocs.io/en/latest/",
        "Source": "https://github.com/mortencombat/stretchable",
        "Tracker": "https://github.com/mortencombat/stretchable/issues"
    },
    "split_keywords": [
        "flexbox",
        " grid",
        " block",
        " stretch",
        " css",
        " layout"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "5ea3e4c2b5420e4618550ea3153cd55921564c87aeb9206007c972c6512334dd",
                "md5": "81f4596f47b39cea3429f601e1586ddd",
                "sha256": "e34e7988129b877bed1f9a247deed174ffed8066ab5b9a3178f18f1102836468"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-macosx_10_12_x86_64.whl",
            "has_sig": false,
            "md5_digest": "81f4596f47b39cea3429f601e1586ddd",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 486719,
            "upload_time": "2025-10-29T22:14:26",
            "upload_time_iso_8601": "2025-10-29T22:14:26.397505Z",
            "url": "https://files.pythonhosted.org/packages/5e/a3/e4c2b5420e4618550ea3153cd55921564c87aeb9206007c972c6512334dd/stretchable-1.1.8-cp38-abi3-macosx_10_12_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6c4dadd8d6cbf3d64dc1ecc2e57d803063d618b623919391f07dd80b06710d40",
                "md5": "713aec5366c62ec9e363313835bd1471",
                "sha256": "a9160a4013a1e37bd4c5dd4a6ea252cec2923cc91ba6bd68b20e7a1a8bfee8be"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-macosx_11_0_arm64.whl",
            "has_sig": false,
            "md5_digest": "713aec5366c62ec9e363313835bd1471",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 467957,
            "upload_time": "2025-10-29T22:14:28",
            "upload_time_iso_8601": "2025-10-29T22:14:28.388646Z",
            "url": "https://files.pythonhosted.org/packages/6c/4d/add8d6cbf3d64dc1ecc2e57d803063d618b623919391f07dd80b06710d40/stretchable-1.1.8-cp38-abi3-macosx_11_0_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "0d0b7b3fbb028d6e82d624b72f9f3cae6a2fd753c994b405d479338a0f0da073",
                "md5": "149341c03aa04d1069d145cb86bc226e",
                "sha256": "173b02e696763d1a1b922c9930d55740940c4a22290e11ae564a7574d175c54b"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "has_sig": false,
            "md5_digest": "149341c03aa04d1069d145cb86bc226e",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 506173,
            "upload_time": "2025-10-29T22:14:30",
            "upload_time_iso_8601": "2025-10-29T22:14:30.269219Z",
            "url": "https://files.pythonhosted.org/packages/0d/0b/7b3fbb028d6e82d624b72f9f3cae6a2fd753c994b405d479338a0f0da073/stretchable-1.1.8-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c8b6bf0bff56102d87f16cf953bf1bef64fd7682fa1533bea9fc42f2e863a9c5",
                "md5": "5ed53f322e86f97db0e4e79c0d31803a",
                "sha256": "1710c748f49c969fb067679847b371f49c1bc9923477a4f8d209029aee485809"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "has_sig": false,
            "md5_digest": "5ed53f322e86f97db0e4e79c0d31803a",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 530062,
            "upload_time": "2025-10-29T22:14:31",
            "upload_time_iso_8601": "2025-10-29T22:14:31.837428Z",
            "url": "https://files.pythonhosted.org/packages/c8/b6/bf0bff56102d87f16cf953bf1bef64fd7682fa1533bea9fc42f2e863a9c5/stretchable-1.1.8-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c5fe5201813989cf8c1c21692ac5afd1a2d36e11741325a46fd6bb1a768452f8",
                "md5": "7c765c3d9614275195f8aba87589eb0b",
                "sha256": "7379be3622844bea7356fab3c83b0b46c10b3b8f5cabc7b2071134f7169c1226"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-musllinux_1_2_aarch64.whl",
            "has_sig": false,
            "md5_digest": "7c765c3d9614275195f8aba87589eb0b",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 686437,
            "upload_time": "2025-10-29T22:14:33",
            "upload_time_iso_8601": "2025-10-29T22:14:33.429261Z",
            "url": "https://files.pythonhosted.org/packages/c5/fe/5201813989cf8c1c21692ac5afd1a2d36e11741325a46fd6bb1a768452f8/stretchable-1.1.8-cp38-abi3-musllinux_1_2_aarch64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "13b6008bebdd0edbb320bc50a1cee93db1fb527b4381005ed3bed08c00d7bbe5",
                "md5": "9e3dbe034d1a0a6852ce2e8a0254b855",
                "sha256": "b031ba4e400599c87ffcb9cd818f3939db6f5434aae49533481842939d507ba8"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-musllinux_1_2_x86_64.whl",
            "has_sig": false,
            "md5_digest": "9e3dbe034d1a0a6852ce2e8a0254b855",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 697126,
            "upload_time": "2025-10-29T22:14:34",
            "upload_time_iso_8601": "2025-10-29T22:14:34.891972Z",
            "url": "https://files.pythonhosted.org/packages/13/b6/008bebdd0edbb320bc50a1cee93db1fb527b4381005ed3bed08c00d7bbe5/stretchable-1.1.8-cp38-abi3-musllinux_1_2_x86_64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "a999e58bb54055265fb01e387d414c33a62733821290cfa266509edb21d6446b",
                "md5": "3c28e21b5285a53cae57b95c2412b091",
                "sha256": "642c8025ec7ea12d37a66fb4cdd5619513efc6f1617cf1cafdb01d49c18a2dc3"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-win32.whl",
            "has_sig": false,
            "md5_digest": "3c28e21b5285a53cae57b95c2412b091",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 347431,
            "upload_time": "2025-10-29T22:14:36",
            "upload_time_iso_8601": "2025-10-29T22:14:36.721642Z",
            "url": "https://files.pythonhosted.org/packages/a9/99/e58bb54055265fb01e387d414c33a62733821290cfa266509edb21d6446b/stretchable-1.1.8-cp38-abi3-win32.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "9382f632174499bf13fec72e58872d1df56abedb8ab36b9a9c4d2940f73a5f0c",
                "md5": "5d86951c4599c41c62533063b0474d60",
                "sha256": "42c10d864b3e8b2cb0ce16a64df6905318c6a78c6269041b1532c606bf6158c3"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-win_amd64.whl",
            "has_sig": false,
            "md5_digest": "5d86951c4599c41c62533063b0474d60",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 356752,
            "upload_time": "2025-10-29T22:14:38",
            "upload_time_iso_8601": "2025-10-29T22:14:38.445233Z",
            "url": "https://files.pythonhosted.org/packages/93/82/f632174499bf13fec72e58872d1df56abedb8ab36b9a9c4d2940f73a5f0c/stretchable-1.1.8-cp38-abi3-win_amd64.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "b86899d5ccda98a60954095f60a49287384d1105cc2ee1d0fec7692f72e549b5",
                "md5": "6f93027e5011afb4b5d57b19abc26d63",
                "sha256": "fec7556c160f73bd200af15570e6d219612499760172cb3485680e1286a7a316"
            },
            "downloads": -1,
            "filename": "stretchable-1.1.8-cp38-abi3-win_arm64.whl",
            "has_sig": false,
            "md5_digest": "6f93027e5011afb4b5d57b19abc26d63",
            "packagetype": "bdist_wheel",
            "python_version": "cp38",
            "requires_python": ">=3.8",
            "size": 334560,
            "upload_time": "2025-10-29T22:14:39",
            "upload_time_iso_8601": "2025-10-29T22:14:39.985121Z",
            "url": "https://files.pythonhosted.org/packages/b8/68/99d5ccda98a60954095f60a49287384d1105cc2ee1d0fec7692f72e549b5/stretchable-1.1.8-cp38-abi3-win_arm64.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-29 22:14:26",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "mortencombat",
    "github_project": "stretchable",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "stretchable"
}