Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7512760
wip(hatch): get bricks from hatch project tomls
DavidVujic Jan 10, 2024
938ee60
wip(hatch): update hatch project tomls with project bricks
DavidVujic Jan 10, 2024
4c0ff7c
wip(cli): add CLI base
DavidVujic Jan 11, 2024
c51220f
wip(poly info): refactor into a commands component, used by cli and p…
DavidVujic Jan 12, 2024
8d9ea42
wip(poly create): refactor into a commands component, used by cli and…
DavidVujic Jan 12, 2024
31ca4ec
wip(poly diff): refactor into a commands component, used by cli and p…
DavidVujic Jan 12, 2024
028b796
wip(poly check): refactor into a commands component, used by cli and …
DavidVujic Jan 12, 2024
4c2a5ff
fix: bump package.lock in poetry plugin project
DavidVujic Jan 12, 2024
e1779e5
wip(poly check): refactor into a commands component, used by cli and …
DavidVujic Jan 13, 2024
f896cdc
fix(poly libs): order summary and list according to components, bricks
DavidVujic Jan 14, 2024
5569c16
wip(poly libs): refactor into a commands component, used by cli and p…
DavidVujic Jan 14, 2024
510ee78
wip(cli): refactor the base, entry points with options
DavidVujic Jan 14, 2024
c2cb066
wip(poly sync): refactor into a commands component, used by cli and p…
DavidVujic Jan 14, 2024
a14044c
feat(cli): add CLI project + rename CLI base
DavidVujic Jan 14, 2024
c40e93a
feat(cli): add CLI project + rename CLI base
DavidVujic Jan 14, 2024
4b2aa9f
refactor: to not always return the same value.
DavidVujic Jan 15, 2024
23fdcbb
refactor: remove expression that always evaluates to true.
DavidVujic Jan 15, 2024
13e3d72
refactor: CodeScene notifies about Excess Number of Function Arguments
DavidVujic Jan 15, 2024
5977dbd
feat(cli): add entry point
DavidVujic Jan 15, 2024
fff12cb
feat(cli): unique top namespace in entry point
DavidVujic Jan 15, 2024
b372909
bump poetry plugin to 1.14.0
DavidVujic Jan 15, 2024
b35f6a5
fix(cli): project name without underscore
DavidVujic Jan 15, 2024
67ce2a7
dev: add cli script entry point for dev testing purposes
DavidVujic Jan 15, 2024
026e269
docs(cli): add step-by-step quick start
DavidVujic Jan 15, 2024
d96225f
docs(cli): add step-by-step quick start
DavidVujic Jan 15, 2024
dcde096
docs(cli): add step-by-step quick start
DavidVujic Jan 15, 2024
3fe1198
docs(cli): add step-by-step quick start
DavidVujic Jan 15, 2024
84e92b1
docs: add info about the new CLI and Hatch support
DavidVujic Jan 15, 2024
e4b41ff
docs: add info about the new CLI and Hatch support
DavidVujic Jan 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ Have a look at the [Python-specific documentation](https://davidvujic.github.io/
You will find installation, setup, usage guides and more.

## Polylith for Python? :snake:
This repo contains a Poetry plugin, that you can install from [PyPI](https://pypi.org/project/poetry-polylith-plugin).
The plugin will add Polylith specific tooling support to Poetry.
Have a look in the [Poetry Polylith Plugin project folder](projects/poetry_polylith_plugin/README.md) with details about the Poetry plugin.
This repo contains a Poetry plugin and a CLI that enables support for Hatch.
Both can be installed from PyPI:
* [a Poetry Plugin](https://pypi.org/project/poetry-polylith-plugin)
* a CLI (coming soon)

The Poetry plugin will add Polylith specific tooling support to Poetry.
The CLI will add tooling support for Polylith, enabling more Package & Dependency Management tools (such as Hatch).

### Use cases

Expand All @@ -39,7 +43,10 @@ More details about how to package libraries in the docs about [Packaging & deplo

## :sparkles: Examples :sparkles:
Have a look at the [Python Polylith Examples](https://github.com/DavidVujic/python-polylith-example) repository.
It is a repository with an example __Python__ setup of the Polylith Architecture.

There is also a [Python Polylith Examples with Hatch](https://github.com/DavidVujic/python-polylith-example-hatch) repository.

The repositories are example __Python__ setups of the Polylith Architecture.
You will find examples of sharing code between different kind of projects, and developer tooling setup such as `mypy` and the `venv`.

## Videos
Expand All @@ -63,6 +70,3 @@ You will find examples of sharing code between different kind of projects, and d
- [Kafka messaging with Python & Polylith](https://davidvujic.blogspot.com/2023/08/kafka-messaging-with-python-and-polylith.html)

<img width="800" alt="poetry-poly-info-example" src="https://github.com/DavidVujic/python-polylith/assets/301286/67df68ed-8fa7-4b25-8e4e-beb71a2024cd">



3 changes: 3 additions & 0 deletions bases/polylith/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from polylith.cli import core

__all__ = ["core"]
3 changes: 3 additions & 0 deletions bases/polylith/cli/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from polylith.cli.core import app

app(prog_name="poly")
115 changes: 115 additions & 0 deletions bases/polylith/cli/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from pathlib import Path

from polylith import commands, info, repo, workspace
from polylith.cli import create, options
from typer import Exit, Option, Typer
from typing_extensions import Annotated

app = Typer()

app.add_typer(
create.app,
name="create",
help="Commands for creating a workspace, bases, components and projects.",
)


@app.command("info")
def info_command(short: Annotated[bool, options.short_workspace] = False):
"""Info about the Polylith workspace."""
commands.info.run(short)


@app.command("check")
def check_command(
strict: Annotated[bool, options.strict] = False,
verbose: Annotated[bool, options.verbose] = False,
quiet: Annotated[bool, options.quiet] = False,
directory: Annotated[str, options.directory] = "",
alias: Annotated[str, options.alias] = "",
):
"""Validates the Polylith workspace."""
root = repo.get_workspace_root(Path.cwd())
ns = workspace.parser.get_namespace_from_config(root)

all_projects_data = info.get_projects_data(root, ns)
only_projects_data = [p for p in all_projects_data if info.is_project(p)]

cli_options = {
"verbose": verbose,
"quiet": quiet,
"strict": strict,
"alias": str.split(alias, ",") if alias else [],
}

dir_path = Path(directory).as_posix() if directory else Path.cwd().name
projects_data = [p for p in only_projects_data if dir_path in p["path"].as_posix()]
results = {commands.check.run(root, ns, p, cli_options) for p in projects_data}

if not all(results):
raise Exit(code=1)


@app.command("diff")
def diff_command(
since: Annotated[str, Option(help="Changed since a specific tag.")] = "",
short: Annotated[bool, options.short] = False,
bricks: Annotated[bool, Option(help="Print changed bricks.")] = False,
):
"""Shows changed bricks compared to the latest git tag."""
commands.diff.run(since, short, bricks)


@app.command("libs")
def libs_command(
strict: Annotated[bool, options.strict] = False,
directory: Annotated[str, options.directory] = "",
alias: Annotated[str, options.alias] = "",
):
"""Show third-party libraries used in the workspace."""
root = repo.get_workspace_root(Path.cwd())
ns = workspace.parser.get_namespace_from_config(root)

projects_data = info.get_projects_data(root, ns)

cli_options = {
"strict": strict,
"alias": str.split(alias, ",") if alias else [],
}

dir_path = Path(directory).as_posix() if directory else Path.cwd().name
projects_data = [p for p in projects_data if dir_path in p["path"].as_posix()]
results = {commands.libs.run(root, ns, p, cli_options) for p in projects_data}

if not all(results):
raise Exit(code=1)


@app.command("sync")
def sync_command(
strict: Annotated[bool, options.strict] = False,
quiet: Annotated[bool, options.quiet] = False,
directory: Annotated[str, options.directory] = "",
verbose: Annotated[str, options.verbose] = "",
):
"""Update pyproject.toml with missing bricks."""
root = repo.get_workspace_root(Path.cwd())
ns = workspace.parser.get_namespace_from_config(root)

projects_data = info.get_projects_data(root, ns)

cli_options = {
"strict": strict,
"quiet": quiet,
"verbose": verbose,
}

dir_path = Path(directory).as_posix() if directory else Path.cwd().name
projects_data = [p for p in projects_data if dir_path in p["path"].as_posix()]

for p in projects_data:
commands.sync.run(root, ns, p, cli_options)


if __name__ == "__main__":
app()
67 changes: 67 additions & 0 deletions bases/polylith/cli/create.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from pathlib import Path
from typing import Union

from polylith import project, repo
from polylith.bricks import base, component
from polylith.commands.create import create
from polylith.workspace.create import create_workspace
from typer import Exit, Option, Typer
from typing_extensions import Annotated

app = Typer()


@app.command("base")
def base_command(
name: Annotated[str, Option(help="Name of the base.")],
description: Annotated[str, Option(help="Description of the base.")] = "",
):
"""Creates a Polylith base."""
create(name, description, base.create_base)


@app.command("component")
def component_command(
name: Annotated[str, Option(help="Name of the component.")],
description: Annotated[str, Option(help="Description of the component.")] = "",
):
"""Creates a Polylith component."""
create(name, description, component.create_component)


def _create_project(root: Path, _ns: str, name: str, description: Union[str, None]):
root_pyproject: dict = project.get_toml(root / repo.default_toml)

if repo.is_poetry(root_pyproject):
template = project.templates.poetry_pyproject
elif repo.is_hatch(root_pyproject):
template = project.templates.hatch_pyproject

if not template:
print("Failed to guess the used Package & Dependency Management")
print(
"Is the root pyproject.toml missing, or are you using a tool not supported by Polylith?"
)
raise Exit(code=1)

project.create_project(root, template, name, description or "")


@app.command("project")
def project_command(
name: Annotated[str, Option(help="Name of the project.")],
description: Annotated[str, Option(help="Description of the project.")] = "",
):
"""Creates a Polylith project."""
create(name, description, _create_project)


@app.command("workspace")
def workspace_command(
name: Annotated[str, Option(help="Name of the workspace.")],
theme: Annotated[str, Option(help="Workspace theme.")] = "tdd",
):
"""Creates a Polylith workspace in the current directory."""
path = Path.cwd()

create_workspace(path, name, theme)
17 changes: 17 additions & 0 deletions bases/polylith/cli/options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typer import Option

alias = Option(
help="alias for third-party libraries, useful when an import differ from the library name"
)
directory = Option(
help="The working directory for the command (defaults to the current working directory)."
)

short = Option(help="Print short view.")
short_workspace = Option(help="Display Workspace Info adjusted for many projects.")
strict = Option(
help="More strict checks when matching name of third-party libraries and imports"
)

verbose = Option(help="More verbose output.")
quiet = Option(help="Do not output any messages.")
11 changes: 7 additions & 4 deletions components/polylith/bricks/base.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
from pathlib import Path
from typing import List, Union
from typing import List

from polylith.bricks import component
from polylith.bricks.brick import create_brick
from polylith.repo import bases_dir
from polylith.test import create_test


def create_base(path: Path, namespace: str, package: str, description: Union[str, None]) -> None:
create_brick(path, bases_dir, namespace, package, description)
create_test(path, bases_dir, namespace, package)
def create_base(path: Path, options: dict) -> None:
extra = {"brick": bases_dir}
base_options = {**options, **extra}

create_brick(path, base_options)
create_test(path, base_options)


def get_bases_data(path: Path, ns: str) -> List[dict]:
Expand Down
19 changes: 7 additions & 12 deletions components/polylith/bricks/brick.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from pathlib import Path
from typing import Union

from polylith.dirs import create_dir
from polylith.files import create_file
Expand All @@ -8,15 +7,11 @@
from polylith.workspace import parser


def create_brick(
root: Path,
brick: str,
namespace: str,
package: str,
description: Union[str, None],
modulename: str = "core",
) -> None:
path_kwargs = {"brick": brick, "namespace": namespace, "package": package}
def create_brick(root: Path, options: dict) -> None:
modulename = options["modulename"]
path_kwargs = {
k: v for k, v in options.items() if k in {"brick", "namespace", "package"}
}

brick_structure = parser.get_brick_structure_from_config(root)
resources_structure = parser.get_resources_structure_from_config(root)
Expand All @@ -26,7 +21,7 @@ def create_brick(

d = create_dir(root, brick_path)
create_file(d, f"{modulename}.py")
create_interface(d, namespace, package, modulename, description)
create_interface(d, options)

if parser.is_readme_generation_enabled(root):
create_brick_readme(root / resources_path, package, brick, description)
create_brick_readme(root / resources_path, options)
13 changes: 7 additions & 6 deletions components/polylith/bricks/component.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
from pathlib import Path
from typing import List, Union
from typing import List

from polylith import workspace
from polylith.bricks.brick import create_brick
from polylith.repo import components_dir
from polylith.test import create_test


def create_component(
path: Path, namespace: str, package: str, description: Union[str, None]
) -> None:
create_brick(path, components_dir, namespace, package, description)
create_test(path, components_dir, namespace, package)
def create_component(path: Path, options: dict) -> None:
extra = {"brick": components_dir}
component_options = {**options, **extra}

create_brick(path, component_options)
create_test(path, component_options)


def is_brick_dir(p: Path) -> bool:
Expand Down
3 changes: 3 additions & 0 deletions components/polylith/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from polylith.commands import check, create, diff, info, libs, sync

__all__ = ["check", "create", "diff", "info", "libs", "sync"]
43 changes: 43 additions & 0 deletions components/polylith/commands/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import importlib.metadata
from pathlib import Path

from polylith import alias, check, distributions


def run(root: Path, ns: str, project_data: dict, options: dict) -> bool:
is_verbose = options["verbose"]
is_quiet = options["quiet"]
is_strict = options["strict"]
library_alias = options["alias"]

third_party_libs = project_data["deps"]
name = project_data["name"]

collected_imports = check.report.collect_all_imports(root, ns, project_data)
dists = importlib.metadata.distributions()

known_aliases = distributions.distributions_packages(dists)
known_aliases.update(alias.parse(library_alias))

extra = alias.pick(known_aliases, third_party_libs)

libs = third_party_libs.union(extra)

details = check.report.create_report(
project_data,
collected_imports,
libs,
is_strict,
)

res = all([not details["brick_diff"], not details["libs_diff"]])

if not is_quiet:
check.report.print_missing_deps(details["brick_diff"], name)
check.report.print_missing_deps(details["libs_diff"], name)

if is_verbose:
check.report.print_brick_imports(details["brick_imports"])
check.report.print_brick_imports(details["third_party_imports"])

return res
Loading