tach


Nametach JSON
Version 0.1.11 PyPI version JSON
download
home_pageNone
SummaryA Python tool to maintain a modular package architecture.
upload_time2024-05-15 23:24:54
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseNone
keywords python module package guard enforcement boundary enforcer domain architecture
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            [![image](https://img.shields.io/pypi/v/tach.svg)](https://pypi.Python.org/pypi/tach)
[![image](https://img.shields.io/pypi/l/tach.svg)](https://pypi.Python.org/pypi/tach)
[![image](https://img.shields.io/pypi/pyversions/tach.svg)](https://pypi.Python.org/pypi/tach)
[![image](https://github.com/gauge-sh/tach/actions/workflows/ci.yml/badge.svg)](https://github.com/gauge-sh/tach/actions/workflows/ci.yml)
[![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
# tach
a Python tool to enforce modular design


[Docs](https://gauge-sh.github.io/tach/)

[Discord](https://discord.gg/DKVksRtuqS) - come say hi!


 <video loop src="https://github.com/Never-Over/tach/assets/10570340/a9d8d4df-d262-4b2b-b69a-adbc30d069aa">Tach Demo</video> 


## What is tach?
`tach` allows you to define boundaries and control dependencies between your Python packages. Each package can also define its public interface.

This enforces a decoupled, modular architecture, and prevents tight coupling.
If a package tries to import from another package that is not listed as a dependency, tach will report an error.
If a package tries to import from another package and does not use its public interface, with `strict: true` set, `tach` will report an error.

`tach` is incredibly lightweight, and has no impact on your runtime. Instead, its checks are performed as a lint check through the CLI.

## Installation
```bash
pip install tach
```

## Quickstart
`tach` comes bundled with a command to set up and define your initial boundaries.
```bash
tach init
```
By running `tach init` from the root of your Python project, `tach` will initialize each top-level Python package. Each package will receive a `package.yml` with a single tag based on the folder name. 
The tool will take into consideration the usages between packages, and write a matching set of dependencies to `tach.yml` in the project root.

If you'd like to incrementally or individually add new packages to your `tach.yml`, you can use:
```bash
tach add [package_or_file]
```
This will create a boundary around the given file or directory, and update your `tach.yml` with the correct set of dependencies.

If you want to sync your `tach.yml` with the actual dependencies found in your project, you can use `tach sync`:
```bash
tach sync [--prune]
```

Any dependency errors will be automatically resolved by
adding the corresponding dependencies to your `tach.yml` file. If you supply `--prune`,
any dependency constraints in your `tach.yml` which are not necessary will also be removed.

In case you want to start over, `tach clean` lets you delete all `tach` configuration files so that you can re-initialize or configure your packages manually.
```bash
tach clean
```



## Defining Packages
To define a package, add a `package.yml` to the corresponding Python package. Add at least one 'tag' to identify the package:
```python
# core/package.yml
tags: ["core"]
```
```python
# db/package.yml
tags: ["db"]
```
```python
# utils/package.yml
tags: ["utils"]
```
Next, specify the constraints for each tag in `tach.yml` in the root of your project:
```yaml
# [root]/tach.yml
constraints:
- tag: core
  depends_on:
  - db
  - utils
- tag: db
  depends_on:
  - utils
- tag: utils
  depends_on: []
```
With these rules in place, packages with tag `core` can import from packages with tag `db` or `utils`. Packages tagged with `db` can only import from `utils`, and packages tagged with `utils` cannot import from any other packages in the project. 

`tach` will now flag any violation of these boundaries.
```bash
# From the root of your Python project (in this example, `project/`)
> tach check
❌ ./utils/helpers.py: Import "core.PublicAPI" is blocked by boundary "core". Tag(s) ["utils"] do not have access to ["core"].
```

## Defining Interfaces
If you want to define a public interface for the package, import and reference each object you want exposed in the package's `__init__.py` and add its name to `__all__`:
```python
# db/__init__.py
from db.service import PublicAPI

__all__ = ["PublicAPI"]
```
Turning on `strict: true` in the package's `package.yml` will then enforce that all imports from this package occur through `__init__.py` and are listed in `__all__`
```yaml
# db/package.yml
tags: ["db"]
strict: true
```
```python
# The only valid import from "db"
from db import PublicAPI 
```

### Pre-Commit Hook
`tach` can be installed as a pre-commit hook. See the [docs](https://gauge-sh.github.io/tach/usage/#tach-install) for installation instructions.


## Advanced
`tach` supports specific exceptions. You can mark an import with the `tach-ignore` comment:
```python
# tach-ignore
from db.main import PrivateAPI
```
This will stop `tach` from flagging this import as a boundary violation.

You can also specify multiple tags for a given package:
```python
# utils/package.yml
tags: ["core", "utils"]
```
This will expand the set of packages that "utils" can access to include all packages that "core" and "utils" `depends_on` as defined in `tach.yml`.

By default, `tach` ignores hidden directories and files (paths starting with `.`). To override this behavior, set `exclude_hidden_paths` in `tach.yml`
```yaml
exclude_hidden_paths: false
```

## Details
`tach` works by analyzing the abstract syntax tree (AST) of your codebase. It has no runtime impact, and all operations are performed statically. 

Boundary violations are detected at the import layer. This means that dynamic imports using `importlib` or similar approaches will not be caught by tach.

[PyPi Package](https://pypi.org/project/tach/)

### License
[GNU GPLv3](LICENSE)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "tach",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "python, module, package, guard, enforcement, boundary, enforcer, domain, architecture",
    "author": null,
    "author_email": "Caelean Barnes <caeleanb@gmail.com>, Evan Doyle <evanmdoyle@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/d6/43/417314cd7852990387373a712e6fe286c340c1bef989609309db6e041ae8/tach-0.1.11.tar.gz",
    "platform": null,
    "description": "[![image](https://img.shields.io/pypi/v/tach.svg)](https://pypi.Python.org/pypi/tach)\n[![image](https://img.shields.io/pypi/l/tach.svg)](https://pypi.Python.org/pypi/tach)\n[![image](https://img.shields.io/pypi/pyversions/tach.svg)](https://pypi.Python.org/pypi/tach)\n[![image](https://github.com/gauge-sh/tach/actions/workflows/ci.yml/badge.svg)](https://github.com/gauge-sh/tach/actions/workflows/ci.yml)\n[![Checked with pyright](https://microsoft.github.io/pyright/img/pyright_badge.svg)](https://microsoft.github.io/pyright/)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n# tach\na Python tool to enforce modular design\n\n\n[Docs](https://gauge-sh.github.io/tach/)\n\n[Discord](https://discord.gg/DKVksRtuqS) - come say hi!\n\n\n <video loop src=\"https://github.com/Never-Over/tach/assets/10570340/a9d8d4df-d262-4b2b-b69a-adbc30d069aa\">Tach Demo</video> \n\n\n## What is tach?\n`tach` allows you to define boundaries and control dependencies between your Python packages. Each package can also define its public interface.\n\nThis enforces a decoupled, modular architecture, and prevents tight coupling.\nIf a package tries to import from another package that is not listed as a dependency, tach will report an error.\nIf a package tries to import from another package and does not use its public interface, with `strict: true` set, `tach` will report an error.\n\n`tach` is incredibly lightweight, and has no impact on your runtime. Instead, its checks are performed as a lint check through the CLI.\n\n## Installation\n```bash\npip install tach\n```\n\n## Quickstart\n`tach` comes bundled with a command to set up and define your initial boundaries.\n```bash\ntach init\n```\nBy running `tach init` from the root of your Python project, `tach` will initialize each top-level Python package. Each package will receive a `package.yml` with a single tag based on the folder name. \nThe tool will take into consideration the usages between packages, and write a matching set of dependencies to `tach.yml` in the project root.\n\nIf you'd like to incrementally or individually add new packages to your `tach.yml`, you can use:\n```bash\ntach add [package_or_file]\n```\nThis will create a boundary around the given file or directory, and update your `tach.yml` with the correct set of dependencies.\n\nIf you want to sync your `tach.yml` with the actual dependencies found in your project, you can use `tach sync`:\n```bash\ntach sync [--prune]\n```\n\nAny dependency errors will be automatically resolved by\nadding the corresponding dependencies to your `tach.yml` file. If you supply `--prune`,\nany dependency constraints in your `tach.yml` which are not necessary will also be removed.\n\nIn case you want to start over, `tach clean` lets you delete all `tach` configuration files so that you can re-initialize or configure your packages manually.\n```bash\ntach clean\n```\n\n\n\n## Defining Packages\nTo define a package, add a `package.yml` to the corresponding Python package. Add at least one 'tag' to identify the package:\n```python\n# core/package.yml\ntags: [\"core\"]\n```\n```python\n# db/package.yml\ntags: [\"db\"]\n```\n```python\n# utils/package.yml\ntags: [\"utils\"]\n```\nNext, specify the constraints for each tag in `tach.yml` in the root of your project:\n```yaml\n# [root]/tach.yml\nconstraints:\n- tag: core\n  depends_on:\n  - db\n  - utils\n- tag: db\n  depends_on:\n  - utils\n- tag: utils\n  depends_on: []\n```\nWith these rules in place, packages with tag `core` can import from packages with tag `db` or `utils`. Packages tagged with `db` can only import from `utils`, and packages tagged with `utils` cannot import from any other packages in the project. \n\n`tach` will now flag any violation of these boundaries.\n```bash\n# From the root of your Python project (in this example, `project/`)\n> tach check\n\u274c ./utils/helpers.py: Import \"core.PublicAPI\" is blocked by boundary \"core\". Tag(s) [\"utils\"] do not have access to [\"core\"].\n```\n\n## Defining Interfaces\nIf you want to define a public interface for the package, import and reference each object you want exposed in the package's `__init__.py` and add its name to `__all__`:\n```python\n# db/__init__.py\nfrom db.service import PublicAPI\n\n__all__ = [\"PublicAPI\"]\n```\nTurning on `strict: true` in the package's `package.yml` will then enforce that all imports from this package occur through `__init__.py` and are listed in `__all__`\n```yaml\n# db/package.yml\ntags: [\"db\"]\nstrict: true\n```\n```python\n# The only valid import from \"db\"\nfrom db import PublicAPI \n```\n\n### Pre-Commit Hook\n`tach` can be installed as a pre-commit hook. See the [docs](https://gauge-sh.github.io/tach/usage/#tach-install) for installation instructions.\n\n\n## Advanced\n`tach` supports specific exceptions. You can mark an import with the `tach-ignore` comment:\n```python\n# tach-ignore\nfrom db.main import PrivateAPI\n```\nThis will stop `tach` from flagging this import as a boundary violation.\n\nYou can also specify multiple tags for a given package:\n```python\n# utils/package.yml\ntags: [\"core\", \"utils\"]\n```\nThis will expand the set of packages that \"utils\" can access to include all packages that \"core\" and \"utils\" `depends_on` as defined in `tach.yml`.\n\nBy default, `tach` ignores hidden directories and files (paths starting with `.`). To override this behavior, set `exclude_hidden_paths` in `tach.yml`\n```yaml\nexclude_hidden_paths: false\n```\n\n## Details\n`tach` works by analyzing the abstract syntax tree (AST) of your codebase. It has no runtime impact, and all operations are performed statically. \n\nBoundary violations are detected at the import layer. This means that dynamic imports using `importlib` or similar approaches will not be caught by tach.\n\n[PyPi Package](https://pypi.org/project/tach/)\n\n### License\n[GNU GPLv3](LICENSE)\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A Python tool to maintain a modular package architecture.",
    "version": "0.1.11",
    "project_urls": {
        "Homepage": "https://github.com/never-over/tach",
        "Issues": "https://github.com/never-over/tach/issues"
    },
    "split_keywords": [
        "python",
        " module",
        " package",
        " guard",
        " enforcement",
        " boundary",
        " enforcer",
        " domain",
        " architecture"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "8d5dab690ef21ca11f966297bfbcad98142dc4084df6cda4808020485d493b50",
                "md5": "fdf2f68d421edad8e8fb1a0eae2a38af",
                "sha256": "684026fd3122ef6c5f5951508c2844c49ea7c93e32e463e9402adef201bf4227"
            },
            "downloads": -1,
            "filename": "tach-0.1.11-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "fdf2f68d421edad8e8fb1a0eae2a38af",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 41840,
            "upload_time": "2024-05-15T23:24:52",
            "upload_time_iso_8601": "2024-05-15T23:24:52.309123Z",
            "url": "https://files.pythonhosted.org/packages/8d/5d/ab690ef21ca11f966297bfbcad98142dc4084df6cda4808020485d493b50/tach-0.1.11-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d643417314cd7852990387373a712e6fe286c340c1bef989609309db6e041ae8",
                "md5": "802ee919343f1f5dbd1966d30d015758",
                "sha256": "f3d32862d1741fa02d7c94ff0a5954d0ab4f61697f8c9ff12dc7c057578783c6"
            },
            "downloads": -1,
            "filename": "tach-0.1.11.tar.gz",
            "has_sig": false,
            "md5_digest": "802ee919343f1f5dbd1966d30d015758",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 133283,
            "upload_time": "2024-05-15T23:24:54",
            "upload_time_iso_8601": "2024-05-15T23:24:54.072681Z",
            "url": "https://files.pythonhosted.org/packages/d6/43/417314cd7852990387373a712e6fe286c340c1bef989609309db6e041ae8/tach-0.1.11.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-05-15 23:24:54",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "never-over",
    "github_project": "tach",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "tach"
}
        
Elapsed time: 0.36233s