├── docs └── source │ ├── _static │ └── .gitkeep │ ├── index.md │ └── conf.py ├── src └── kedro_init │ ├── py.typed │ ├── __init__.py │ ├── templates │ ├── settings.py │ └── pipeline_registry.py │ ├── config_dirs.py │ ├── cli.py │ ├── init.py │ ├── modules.py │ └── build_config.py ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── .github └── workflows │ ├── rtd-preview.yml │ ├── test.yml │ └── publish.yml ├── .copier-answers.yml ├── tests └── test_modules.py ├── CONTRIBUTING.md ├── LICENSE ├── tox.ini ├── README.md ├── pyproject.toml ├── .gitignore └── CODE_OF_CONDUCT.md /docs/source/_static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/kedro_init/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561 2 | -------------------------------------------------------------------------------- /src/kedro_init/__init__.py: -------------------------------------------------------------------------------- 1 | """Initialise a Kedro project from an existing Python package.""" 2 | 3 | from __future__ import annotations 4 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | ```{include} ../../README.md 2 | :relative-images: 3 | ``` 4 | 5 | ```{toctree} 6 | :caption: 'Contents:' 7 | :maxdepth: 2 8 | ``` 9 | 10 | # Indices and tables 11 | 12 | - {ref}`genindex` 13 | - {ref}`modindex` 14 | - {ref}`search` 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.3.0 4 | hooks: 5 | - id: check-added-large-files 6 | - id: check-merge-conflict 7 | 8 | - repo: https://github.com/astral-sh/ruff-pre-commit 9 | rev: v0.3.2 10 | hooks: 11 | - id: ruff 12 | args: [ --fix, --exit-non-zero-on-fix ] 13 | - id: ruff-format 14 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | version: 2 4 | 5 | build: 6 | os: ubuntu-22.04 7 | tools: 8 | python: "3.9" 9 | 10 | sphinx: 11 | fail_on_warning: true 12 | 13 | python: 14 | install: 15 | - method: pip 16 | path: . 17 | extra_requirements: 18 | - doc 19 | -------------------------------------------------------------------------------- /.github/workflows/rtd-preview.yml: -------------------------------------------------------------------------------- 1 | name: Read the Docs Pull Request Preview 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | 7 | permissions: 8 | pull-requests: write 9 | 10 | jobs: 11 | documentation-links: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: readthedocs/actions/preview@v1 15 | with: 16 | project-slug: "kedro-init" 17 | -------------------------------------------------------------------------------- /.copier-answers.yml: -------------------------------------------------------------------------------- 1 | # Changes here will be overwritten by Copier; NEVER EDIT MANUALLY 2 | _commit: v2024.03.1 3 | _src_path: gh:astrojuanlu/copier-pylib 4 | author_email: juan_luis_cano@mckinsey.com 5 | author_name: Juan Luis Cano Rodríguez 6 | github_org: astrojuanlu 7 | package_name: kedro_init 8 | project_name: kedro-init 9 | short_description: A simple CLI command that initialises a Kedro project from an existing 10 | Python package 11 | -------------------------------------------------------------------------------- /src/kedro_init/templates/settings.py: -------------------------------------------------------------------------------- 1 | """Project settings. 2 | 3 | There is no need to edit this file 4 | unless you want to change values from the Kedro defaults. 5 | For further information, including these default values, see 6 | https://docs.kedro.org/en/stable/kedro_project_setup/settings.html. 7 | """ 8 | 9 | from kedro.config import OmegaConfigLoader 10 | 11 | CONFIG_LOADER_CLASS = OmegaConfigLoader 12 | CONFIG_LOADER_ARGS = { 13 | "base_env": "base", 14 | "default_run_env": "local", 15 | } 16 | -------------------------------------------------------------------------------- /src/kedro_init/templates/pipeline_registry.py: -------------------------------------------------------------------------------- 1 | """Project pipelines.""" 2 | 3 | from kedro.framework.project import find_pipelines 4 | from kedro.pipeline import Pipeline 5 | 6 | 7 | def register_pipelines() -> dict[str, Pipeline]: 8 | """Register the project's pipelines. 9 | 10 | Returns: 11 | A mapping from pipeline names to ``Pipeline`` objects. 12 | """ 13 | pipelines = find_pipelines() 14 | # https://github.com/kedro-org/kedro/issues/2526 15 | pipelines["__default__"] = sum(pipelines.values(), start=Pipeline([])) 16 | return pipelines 17 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | on: push 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | python-version: ["3.9", "3.10", "3.11", "3.12"] 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Set up Python ${{ matrix.python-version }} 13 | uses: actions/setup-python@v2 14 | with: 15 | python-version: ${{ matrix.python-version }} 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install uv 19 | uv pip install --system tox tox-uv tox-gh-actions 20 | - name: Test with tox 21 | run: tox 22 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | """Sphinx configuration.""" 2 | 3 | from importlib.metadata import metadata 4 | 5 | # -- Project information 6 | 7 | _metadata = metadata("kedro-init") 8 | 9 | project = _metadata["Name"] 10 | author = _metadata["Author-email"].split("<", 1)[0].strip() 11 | copyright = f"2023, {author}" 12 | 13 | version = _metadata["Version"] 14 | release = ".".join(version.split(".")[:2]) 15 | 16 | 17 | # -- General configuration 18 | 19 | extensions = [ 20 | "myst_parser", 21 | "sphinx_copybutton", 22 | ] 23 | 24 | templates_path = ["_templates"] 25 | 26 | exclude_patterns = [ 27 | "Thumbs.db", 28 | ".DS_Store", 29 | ".ipynb_checkpoints", 30 | ] 31 | 32 | # -- Options for HTML output 33 | 34 | html_theme = "furo" 35 | html_static_path = ["_static"] 36 | -------------------------------------------------------------------------------- /src/kedro_init/config_dirs.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | CONFIG_DIRS = [ 6 | Path("conf") / "base", 7 | Path("conf") / "local", 8 | ] 9 | 10 | 11 | def get_or_create_config_dirs( 12 | project_root: Path, *, expected_config_dirs: list[Path] | None = None 13 | ) -> dict[Path, tuple[bool, Path]]: 14 | if not expected_config_dirs: 15 | expected_config_dirs = CONFIG_DIRS 16 | 17 | config_dirs = {} 18 | for config_dir in expected_config_dirs: 19 | target_config_dir = project_root / config_dir 20 | if target_config_dir.is_dir(): 21 | config_dirs[config_dir] = True, target_config_dir 22 | else: 23 | config_dirs[config_dir] = False, target_config_dir 24 | 25 | return config_dirs 26 | 27 | 28 | def init_config_dir(project_root: Path, *, target_config_dir: Path) -> None: 29 | target_config_dir.mkdir(parents=True, exist_ok=True) 30 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish library 2 | 3 | on: 4 | push: 5 | tags: 6 | # Don't try to be smart about PEP 440 compliance, 7 | # see https://www.python.org/dev/peps/pep-0440/#appendix-b-parsing-version-strings-with-regular-expressions 8 | - v* 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | environment: release 14 | permissions: 15 | id-token: write 16 | contents: write 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up Python 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: '3.9' 23 | - name: Install build dependencies 24 | run: python -m pip install build 25 | - name: Build package 26 | run: python -m build 27 | - name: Publish to PyPI 28 | uses: pypa/gh-action-pypi-publish@release/v1 29 | - name: Create GitHub release 30 | uses: softprops/action-gh-release@v1 31 | -------------------------------------------------------------------------------- /tests/test_modules.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from kedro_init.modules import TEMPLATES_PATH, get_or_create_modules 3 | 4 | 5 | @pytest.fixture 6 | def project_setup(tmp_path): 7 | package_name = "test_package" 8 | src_dir = tmp_path / "src" / package_name 9 | src_dir.mkdir(parents=True) 10 | return tmp_path, package_name 11 | 12 | 13 | def test_get_or_create_modules_gets_expected_result(project_setup): 14 | project_root, package_name = project_setup 15 | build_config = {"package_name": package_name} 16 | 17 | result = get_or_create_modules(project_root, build_config=build_config) 18 | 19 | assert result == { 20 | "pipeline_registry.py": ( 21 | False, 22 | project_root / f"src/{package_name}/pipeline_registry.py", 23 | TEMPLATES_PATH / "pipeline_registry.py", 24 | ), 25 | "settings.py": ( 26 | False, 27 | project_root / f"src/{package_name}/settings.py", 28 | TEMPLATES_PATH / "settings.py", 29 | ), 30 | } 31 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing guidelines 2 | 3 | ## Before contributing 4 | 5 | Welcome to kedro-init! Before contributing to the project, 6 | make sure that you **read our code of conduct** (`CODE_OF_CONDUCT.md`). 7 | 8 | ## Contributing code 9 | 10 | 1. Clone the repository 11 | 2. Set up and activate a Python development environment 12 | (advice: use [venv](https://docs.python.org/3/library/venv.html), 13 | [virtualenv](https://virtualenv.pypa.io/), or [miniconda](https://docs.conda.io/en/latest/miniconda.html)) 14 | 3. Install tox: `python -m pip install tox` 15 | 4. Make sure the tests run: `tox -e py39` 16 | (change the version number according to the Python you are using) 17 | 5. Start a new branch: `git switch -c new-branch main` 18 | 6. Make your code changes 19 | 7. Check that your code follows the style guidelines of the project: `tox -e reformat && tox -e check` 20 | 8. Run the tests again and verify that they pass: `tox -e py39` 21 | 9. (optional) Build the documentation: `tox -e docs` 22 | 10. Commit, push, and open a pull request! 23 | -------------------------------------------------------------------------------- /src/kedro_init/cli.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import click 4 | 5 | try: 6 | from rich.console import Console 7 | 8 | rich_available = True 9 | except ImportError: 10 | rich_available = False 11 | 12 | from .init import init, init_steps 13 | 14 | 15 | @click.command() 16 | @click.argument("project_root", type=click.Path(exists=False)) 17 | def cli(project_root: str) -> None: 18 | project_root_path = Path(project_root) 19 | if rich_available: 20 | console = Console() 21 | with console.status( 22 | f"Initialising Kedro project in {project_root_path.absolute()}" 23 | ): 24 | for step_message in init_steps(project_root_path): 25 | console.log(step_message) 26 | console.log( 27 | "[green]:large_orange_diamond: Kedro project successfully initialised!" 28 | ) 29 | else: 30 | click.echo(f"Initialising Kedro project in {project_root_path.absolute()}...") 31 | init(project_root_path) 32 | click.echo("\U0001f536 Kedro project successfully initialised!") 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 by Juan Luis Cano Rodríguez 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /src/kedro_init/init.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | from pathlib import Path 3 | 4 | from .build_config import get_or_create_build_config, init_build_config 5 | from .config_dirs import get_or_create_config_dirs, init_config_dir 6 | from .modules import get_or_create_modules, init_module 7 | 8 | 9 | def init_steps(project_root: Path) -> t.Generator[str, None, None]: 10 | yield "Looking for existing package directories" 11 | 12 | existing, build_config = get_or_create_build_config(project_root) 13 | if not existing: 14 | init_build_config(project_root, build_config=build_config) 15 | 16 | yield "Initialising config directories" 17 | 18 | config_dirs = get_or_create_config_dirs(project_root) 19 | for existing, target_config_dir in config_dirs.values(): 20 | if not existing: 21 | init_config_dir(project_root, target_config_dir=target_config_dir) 22 | 23 | yield "Creating modules" 24 | 25 | modules = get_or_create_modules(project_root, build_config=build_config) 26 | for existing, target_module_path, module_contents_path in modules.values(): 27 | if not existing: 28 | init_module( 29 | project_root, 30 | target_module_path=target_module_path, 31 | module_contents_path=module_contents_path, 32 | ) 33 | 34 | 35 | def init(project_root: Path) -> None: 36 | for _ in init_steps(project_root): 37 | pass 38 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | check 4 | docs 5 | {py39,py310,py311,py312,pypy3}{,-coverage} 6 | # See https://tox.readthedocs.io/en/latest/example/package.html#flit 7 | isolated_build = True 8 | isolated_build_env = build 9 | 10 | [gh-actions] 11 | python = 12 | 3.9: check, py39 13 | 3.10: py310 14 | 3.11: py310 15 | 3.12: py310 16 | 17 | [testenv] 18 | basepython = 19 | pypy3: pypy3 20 | py39: python3.9 21 | py310: python3.10 22 | py311: python3.11 23 | py312: python3.12 24 | # See https://github.com/tox-dev/tox/issues/1548 25 | {check,docs,build}: python3 26 | setenv = 27 | PYTHONUNBUFFERED = yes 28 | PYTEST_EXTRA_ARGS = -s 29 | coverage: PYTEST_EXTRA_ARGS = --cov 30 | passenv = 31 | * 32 | extras = 33 | test 34 | commands = 35 | mypy src tests 36 | pytest {env:PYTEST_MARKERS:} {env:PYTEST_EXTRA_ARGS:} {posargs:-vv} 37 | 38 | [testenv:check] 39 | description = perform style checks 40 | deps = 41 | build 42 | pre-commit 43 | skip_install = true 44 | commands = 45 | pre-commit install 46 | pre-commit run --all-files --show-diff-on-failure 47 | python -m build 48 | 49 | [testenv:docs] 50 | description = build HTML docs 51 | setenv = 52 | READTHEDOCS_PROJECT = kedro_init 53 | READTHEDOCS_VERSION = latest 54 | extras = 55 | doc 56 | commands = 57 | sphinx-build -d "{toxworkdir}/docs_doctree" docs/source "{toxworkdir}/docs_out" --color -vW -bhtml 58 | -------------------------------------------------------------------------------- /src/kedro_init/modules.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from pathlib import Path 4 | 5 | TEMPLATES_PATH = Path(__file__).parent / "templates" 6 | MODULE_TEMPLATES = { 7 | "pipeline_registry.py": TEMPLATES_PATH / "pipeline_registry.py", 8 | "settings.py": TEMPLATES_PATH / "settings.py", 9 | } 10 | 11 | 12 | def get_or_create_modules( 13 | project_root: Path, 14 | *, 15 | build_config: dict[str, str], 16 | module_templates: dict[str, Path] | None = None, 17 | ) -> dict[str, tuple[bool, Path, Path]]: 18 | if module_templates: 19 | module_templates_l = module_templates 20 | else: 21 | module_templates_l = MODULE_TEMPLATES 22 | 23 | package_name = build_config["package_name"] 24 | package_dir = project_root / package_name 25 | if not package_dir.is_dir(): 26 | package_dir = next(project_root.glob(f"*/{package_name}"), None) # type: ignore 27 | 28 | if package_dir is None: 29 | raise ValueError( 30 | f"No suitable directory found for package name '{package_name}'" 31 | ) 32 | 33 | modules = {} 34 | for module_name in module_templates_l: 35 | target_module = package_dir / module_name 36 | if target_module.exists(): 37 | modules[module_name] = True, target_module, target_module 38 | else: 39 | modules[module_name] = False, target_module, module_templates_l[module_name] 40 | 41 | return modules 42 | 43 | 44 | def init_module( 45 | project_root: Path, *, target_module_path: Path, module_contents_path: Path 46 | ) -> None: 47 | with target_module_path.open("w") as fh: 48 | fh.write(module_contents_path.read_text()) 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kedro-init 2 | 3 | [![Documentation Status](https://readthedocs.org/projects/kedro-init/badge/?version=latest)](https://kedro-init.readthedocs.io/en/latest/?badge=latest) 4 | [![Code style: ruff-format](https://img.shields.io/badge/code%20style-ruff_format-6340ac.svg)](https://github.com/astral-sh/ruff) 5 | [![PyPI](https://img.shields.io/pypi/v/kedro-init)](https://pypi.org/project/kedro-init) 6 | 7 | A simple CLI command that initialises a Kedro project from an existing Python package 8 | 9 | ## Installation and usage 10 | 11 | To use, run 12 | 13 | ``` 14 | $ uvx kedro-init . 15 | ``` 16 | 17 | Or, alternatively, 18 | 19 | ``` 20 | $ pipx run kedro-init . 21 | ``` 22 | 23 | For example, from a Poetry package: 24 | 25 | ``` 26 | (.venv) $ poetry new --src test-project && cd test-project 27 | (.venv) $ kedro-init . 28 | [00:19:38] Looking for existing package directories cli.py:25 29 | [00:19:45] Initialising config directories cli.py:25 30 | Creating modules cli.py:25 31 | 🔶 Kedro project successfully initialised! cli.py:26 32 | ``` 33 | 34 | And with a uv package: 35 | 36 | ``` 37 | $ mkdir test-project && cd test-project && uv init 38 | Initialized project `test-project` 39 | $ uvx kedro-init . 40 | [09:06:24] Looking for existing package directories cli.py:25 41 | [09:06:25] Initialising config directories cli.py:25 42 | Creating modules cli.py:25 43 | 🔶 Kedro project successfully initialised! cli.py:26 44 | ``` 45 | 46 | ## Development 47 | 48 | To run style checks: 49 | 50 | ``` 51 | (.venv) $ pip install tox 52 | (.venv) $ tox 53 | ``` 54 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["pdm-backend"] 3 | build-backend = "pdm.backend" 4 | 5 | [project] 6 | name = "kedro-init" 7 | readme = "README.md" 8 | requires-python = ">=3.9" 9 | license = {file = "LICENSE"} 10 | description = "A simple CLI command that initialises a Kedro project from an existing Python package" 11 | dependencies = [ 12 | "click", 13 | "installer", 14 | "kedro>=0.18.14", # Mostly arbitrary, but ensures compatibility 15 | "pygetimportables>=0.2.1", 16 | "tomlkit", 17 | ] 18 | authors = [ 19 | {name = "Juan Luis Cano Rodríguez", email = "juan_luis_cano@mckinsey.com"} 20 | ] 21 | classifiers = [ 22 | "Development Status :: 3 - Alpha", 23 | "Intended Audience :: Developers", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | "Programming Language :: Python", 27 | "Programming Language :: Python :: 3", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | "Programming Language :: Python :: 3.12", 32 | ] 33 | dynamic = ["version"] 34 | 35 | [project.urls] 36 | source = "https://github.com/astrojuanlu/kedro-init" 37 | tracker = "https://github.com/astrojuanlu/kedro-init/issues" 38 | documentation = "https://kedro-init.readthedocs.io" 39 | 40 | [project.scripts] 41 | kedro-init = "kedro_init.cli:cli" 42 | 43 | [project.optional-dependencies] 44 | rich = [ 45 | "rich", 46 | ] 47 | test = [ 48 | "mypy", 49 | "hypothesis", 50 | "pytest", 51 | "pytest-cov", 52 | ] 53 | doc = [ 54 | "furo", 55 | "myst-parser", 56 | "sphinx>=5", 57 | "sphinx-copybutton", 58 | ] 59 | 60 | [tool.pdm.version] 61 | source = "scm" 62 | 63 | [tool.ruff] 64 | show-fixes = true 65 | 66 | [tool.ruff.lint] 67 | select = [ 68 | "F", # Pyflakes 69 | "E", # Pycodestyle 70 | "W", # Pycodestyle 71 | "D", # Pydocstyle 72 | "UP", # pyupgrade 73 | "I", # isort 74 | "PL", # Pylint 75 | ] 76 | ignore = ["D100", "D103"] 77 | 78 | [tool.ruff.lint.pydocstyle] 79 | convention = "pep257" 80 | 81 | [tool.ruff.lint.per-file-ignores] 82 | "tests/**/*" = ["D", "PLR2004"] 83 | 84 | [tool.mypy] 85 | python_version = "3.9" 86 | warn_redundant_casts = true 87 | warn_unused_configs = true 88 | pretty = true 89 | show_error_codes = true 90 | show_error_context = true 91 | show_column_numbers = true 92 | 93 | disallow_any_generics = true 94 | disallow_subclassing_any = true 95 | disallow_untyped_calls = true 96 | disallow_incomplete_defs = true 97 | check_untyped_defs = true 98 | disallow_untyped_decorators = true 99 | no_implicit_optional = true 100 | warn_unused_ignores = true 101 | warn_return_any = true 102 | no_implicit_reexport = true 103 | 104 | # More strict checks for library code 105 | [[tool.mypy.overrides]] 106 | module = "kedro_init" 107 | disallow_untyped_defs = true 108 | 109 | # Ignore certain missing imports 110 | # [[tool.mypy.overrides]] 111 | # module = "thirdparty.*" 112 | # ignore_missing_imports = true 113 | 114 | [tool.pytest.ini_options] 115 | testpaths = [ 116 | "tests", 117 | ] 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=python,jupyternotebooks 4 | 5 | ### JupyterNotebooks ### 6 | # gitignore template for Jupyter Notebooks 7 | # website: http://jupyter.org/ 8 | 9 | .ipynb_checkpoints 10 | */.ipynb_checkpoints/* 11 | 12 | # IPython 13 | profile_default/ 14 | ipython_config.py 15 | 16 | # Remove previous ipynb_checkpoints 17 | # git rm -r .ipynb_checkpoints/ 18 | 19 | ### Python ### 20 | # Byte-compiled / optimized / DLL files 21 | __pycache__/ 22 | *.py[cod] 23 | *$py.class 24 | 25 | # C extensions 26 | *.so 27 | 28 | # Distribution / packaging 29 | .Python 30 | build/ 31 | develop-eggs/ 32 | dist/ 33 | downloads/ 34 | eggs/ 35 | .eggs/ 36 | lib/ 37 | lib64/ 38 | parts/ 39 | sdist/ 40 | var/ 41 | wheels/ 42 | share/python-wheels/ 43 | *.egg-info/ 44 | .installed.cfg 45 | *.egg 46 | MANIFEST 47 | 48 | # PyInstaller 49 | # Usually these files are written by a python script from a template 50 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 51 | *.manifest 52 | *.spec 53 | 54 | # Installer logs 55 | pip-log.txt 56 | pip-delete-this-directory.txt 57 | 58 | # Unit test / coverage reports 59 | htmlcov/ 60 | .tox/ 61 | .nox/ 62 | .coverage 63 | .coverage.* 64 | .cache 65 | nosetests.xml 66 | coverage.xml 67 | *.cover 68 | *.py,cover 69 | .hypothesis/ 70 | .pytest_cache/ 71 | cover/ 72 | 73 | # Translations 74 | *.mo 75 | *.pot 76 | 77 | # Django stuff: 78 | *.log 79 | local_settings.py 80 | db.sqlite3 81 | db.sqlite3-journal 82 | 83 | # Flask stuff: 84 | instance/ 85 | .webassets-cache 86 | 87 | # Scrapy stuff: 88 | .scrapy 89 | 90 | # Sphinx documentation 91 | docs/_build/ 92 | 93 | # PyBuilder 94 | .pybuilder/ 95 | target/ 96 | 97 | # Jupyter Notebook 98 | 99 | # IPython 100 | 101 | # pyenv 102 | # For a library or package, you might want to ignore these files since the code is 103 | # intended to run in multiple environments; otherwise, check them in: 104 | # .python-version 105 | 106 | # pipenv 107 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 108 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 109 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 110 | # install all needed dependencies. 111 | #Pipfile.lock 112 | 113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 114 | __pypackages__/ 115 | 116 | # Celery stuff 117 | celerybeat-schedule 118 | celerybeat.pid 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # Environments 124 | .env 125 | .venv 126 | env/ 127 | venv/ 128 | ENV/ 129 | env.bak/ 130 | venv.bak/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # End of https://www.toptal.com/developers/gitignore/api/python,jupyternotebooks 157 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at juan_luis_cano@mckinsey.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/kedro_init/build_config.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import tempfile 4 | import typing as t 5 | import zipfile 6 | from pathlib import Path 7 | 8 | from installer.sources import WheelFile 9 | from installer.utils import parse_metadata_file 10 | 11 | try: 12 | from importlib.metadata import PackageNotFoundError, version 13 | except ModuleNotFoundError: 14 | from importlib_metadata import PackageNotFoundError, version # type: ignore 15 | 16 | import tomlkit 17 | from pygetimportables import _simple_build_wheel, get_top_importables_from_wheel 18 | from validate_pyproject import api, errors, plugins 19 | from validate_pyproject.types import Schema 20 | 21 | 22 | def _get_importables_and_project_name( 23 | project_root: str | Path, outdir: str | Path 24 | ) -> tuple[set[str], str]: 25 | wheel_path = _simple_build_wheel(project_root, outdir) 26 | 27 | with zipfile.ZipFile(wheel_path, "r") as zf: 28 | wheel_file = WheelFile(zf) 29 | metadata = parse_metadata_file(wheel_file.read_dist_info("METADATA")) 30 | 31 | package_names = get_top_importables_from_wheel(wheel_path) 32 | project_name = metadata["Name"] 33 | return package_names, project_name 34 | 35 | 36 | def kedro_pyproject(tool_name: str) -> Schema: 37 | return Schema( 38 | { 39 | "$id": "https://docs.kedro.org/en/latest/", 40 | "type": "object", 41 | "description": "Kedro project metadata", 42 | "properties": { 43 | "package_name": {"type": "string"}, 44 | "project_name": {"type": "string", "format": "pep508-identifier"}, 45 | "kedro_init_version": {"type": "string", "format": "pep440"}, 46 | "source_dir": {"type": "string"}, 47 | }, 48 | "required": ["package_name", "project_name", "kedro_init_version"], 49 | "additionalProperties": False, 50 | } 51 | ) 52 | 53 | 54 | def get_or_create_build_config(project_root: Path) -> tuple[bool, t.Any]: 55 | try: 56 | kedro_version = version("kedro") 57 | except PackageNotFoundError as exc: 58 | raise ValueError("Kedro is not installed") from exc 59 | 60 | available_plugins = [ 61 | *plugins.list_from_entry_points(), 62 | plugins.PluginWrapper("kedro", kedro_pyproject), 63 | ] 64 | validator = api.Validator(available_plugins) 65 | 66 | with (project_root / "pyproject.toml").open("r") as fh: 67 | pyproject_toml = tomlkit.load(fh) 68 | 69 | if not pyproject_toml.get("tool", {}).get("kedro", {}): 70 | with tempfile.TemporaryDirectory() as outdir: 71 | package_names, project_name = _get_importables_and_project_name( 72 | project_root, outdir 73 | ) 74 | if len(package_names) == 1: 75 | package_name = package_names.pop() 76 | else: 77 | raise ValueError("More than one package found in project root") 78 | 79 | kedro_config = { 80 | "project_name": project_name, 81 | "package_name": package_name, 82 | "kedro_init_version": kedro_version, 83 | } 84 | if (project_root / package_name).is_dir(): 85 | kedro_config["source_dir"] = "" 86 | else: 87 | # FIXME: What happens if package_dir is None? (See type: ignore below) 88 | package_dir = next(project_root.glob(f"*/{package_name}"), None) 89 | source_dir = package_dir.parent.name if package_dir is not None else None 90 | if package_dir is not None and source_dir != "src": 91 | kedro_config["source_dir"] = source_dir # type: ignore 92 | return False, kedro_config 93 | 94 | # Kedro build config might be present, return it if valid 95 | try: 96 | validator(pyproject_toml) 97 | except errors.ValidationError as exc: 98 | raise ValueError("Kedro build configuration is invalid") from exc 99 | else: 100 | return True, pyproject_toml["tool"]["kedro"] # type: ignore 101 | 102 | 103 | def init_build_config(project_root: Path, *, build_config: dict[str, str]) -> None: 104 | with (project_root / "pyproject.toml").open("r") as fh: 105 | pyproject_toml = tomlkit.load(fh) 106 | 107 | pyproject_toml.setdefault("tool", {})["kedro"] = build_config 108 | 109 | with (project_root / "pyproject.toml").open("w") as fh: 110 | tomlkit.dump(pyproject_toml, fh) 111 | --------------------------------------------------------------------------------