diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af04301..81cf64d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,22 +22,34 @@ jobs: run: | uv pip compile pyproject.toml -o requirements.txt uv pip compile pyproject.toml --extra dev -o requirements-dev.txt + uv venv uv pip sync requirements.txt requirements-dev.txt - - name: Run MyPy - run: uv pip run mypy src - + - name: Determine module name + id: module + run: | + if [ -d "src" ]; then + echo "name=src" >> $GITHUB_OUTPUT + else + MODULE_NAME=$(basename $(find . -maxdepth 1 -type d -not -path "*/\.*" -not -path "./tests" -not -path "./scripts" -not -path "./docker" -not -path "." | sort | head -1)) + echo "name=$MODULE_NAME" >> $GITHUB_OUTPUT + fi + - name: Run Linter - run: uv pip run ruff check src + run: uv run -m ruff check --fix ${{ steps.module.outputs.name }} - name: Run Formatter - run: uv pip run ruff format src + run: uv run -m ruff format ${{ steps.module.outputs.name }} - name: Run Tests - run: uv pip run pytest tests --cov=src --cov-report=term-missing --cov-report=xml + run: uv run -m pytest tests --cov=${{ steps.module.outputs.name }} --cov-report=term-missing --cov-report=xml + + - name: Run MyPy + run: uv run -m mypy ${{ steps.module.outputs.name }} - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 + continue-on-error: true with: files: coverage.xml - fail_ci_if_error: true + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 0271efd..a85a2c2 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ docker/.env .pytest_cache/* .ruff_cache/* .aider* +CLAUDE.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1cab3cc..4766019 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,6 @@ repos: - repo: local hooks: - - id: mypy - name: Run MyPy - entry: make mypy - language: system - always_run: true - pass_filenames: false - id: lint name: Run Linter entry: make lint @@ -25,3 +19,9 @@ repos: language: system always_run: true pass_filenames: false + - id: mypy + name: Run MyPy + entry: make mypy + language: system + always_run: true + pass_filenames: false diff --git a/Makefile b/Makefile index c729cfd..b6f5232 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ .PHONY: compile-deps setup clean-pyc clean-test clean-venv clean test mypy lint format check clean-example dev-env refresh-containers rebuild-images build-image push-image +# Module name - will be updated by init script +MODULE_NAME := src + # Development Setup ################# compile-deps: # Compile dependencies from pyproject.toml @@ -46,18 +49,18 @@ clean: clean-pyc clean-test clean-venv # Testing and Quality Checks ######################### test: setup # Run pytest with coverage - uv run -m pytest tests --cov=src --cov-report=term-missing + uv run -m pytest tests --cov=$(MODULE_NAME) --cov-report=term-missing mypy: setup # Run type checking - uv run -m mypy src + uv run -m mypy $(MODULE_NAME) -lint: setup # Run ruff linter - uv run -m ruff check src +lint: setup # Run ruff linter with auto-fix + uv run -m ruff check --fix $(MODULE_NAME) format: setup # Run ruff formatter - uv run -m ruff format src + uv run -m ruff format $(MODULE_NAME) -check: setup test mypy lint format # Run all quality checks +check: setup lint format test mypy # Run all quality checks # Project Management ################## diff --git a/scripts/init_project.py b/scripts/init_project.py index 6288455..cf45d07 100755 --- a/scripts/init_project.py +++ b/scripts/init_project.py @@ -75,6 +75,15 @@ def main() -> None: "Author email", get_git_config("email") or "your.email@example.com" ) + # Update project information + print("๐Ÿ“ Updating project configuration...") + update_pyproject_toml( + project_name, + project_description, + author_name, + author_email + ) + # Handle example code code_choice = prompt_with_default( "How would you like to handle example code?\n" @@ -84,10 +93,63 @@ def main() -> None: "Choose option", "1" ) + # Create module directory with project name (replacing src) + project_module_name = project_name.replace("-", "_").lower() + + # Always update the Makefile to use the new module name + print(f"๐Ÿ”ง Updating Makefile to use module name: {project_module_name}") + makefile_path = Path("Makefile") + with open(makefile_path, "r") as f: + makefile_content = f.read() + + # Replace module name in Makefile + updated_makefile = makefile_content.replace("MODULE_NAME := src", f"MODULE_NAME := {project_module_name}") + + with open(makefile_path, "w") as f: + f.write(updated_makefile) + + # Always update pyproject.toml to point to the new module directory + print(f"๐Ÿ“ฆ Updating pyproject.toml for module: {project_module_name}") + pyproject_path = Path("pyproject.toml") + with open(pyproject_path, "rb") as f: + config = tomli.load(f) + + # Update packages from src to new module name + if "tool" in config and "hatch" in config["tool"] and "build" in config["tool"]["hatch"] and "targets" in config["tool"]["hatch"]["build"] and "wheel" in config["tool"]["hatch"]["build"]["targets"]: + config["tool"]["hatch"]["build"]["targets"]["wheel"]["packages"] = [project_module_name] + + with open(pyproject_path, "wb") as f: + tomli_w.dump(config, f) + + # Create the new module directory if it doesn't exist + if not os.path.exists(project_module_name): + print(f"๐Ÿ“ Creating module directory: {project_module_name}") + os.mkdir(project_module_name) + # Create __init__.py + with open(f"{project_module_name}/__init__.py", "w") as f: + f.write(f'"""Main package for {project_name}."""\n') + + # Copy src content to new module directory if src exists + if os.path.exists("src") and project_module_name != "src": + print(f"๐Ÿ“ฆ Copying content from src to {project_module_name}...") + for item in os.listdir("src"): + src_path = os.path.join("src", item) + dest_path = os.path.join(project_module_name, item) + + if os.path.isfile(src_path): + with open(src_path, "r") as src_file: + content = src_file.read() + with open(dest_path, "w") as dest_file: + dest_file.write(content) + + # Remove the old src directory after copying + print("๐Ÿ—‘๏ธ Removing old src directory...") + run_command("rm -rf src") + if code_choice == "2": print("๐Ÿ“ Creating minimal placeholder test...") - # Create minimal src module - with open("src/example.py", "w") as f: + # Create minimal module + with open(f"{project_module_name}/example.py", "w") as f: f.write("""def add(a: int, b: int) -> int: \"\"\"Add two numbers.\"\"\" return a + b @@ -95,7 +157,7 @@ def main() -> None: # Create minimal test with open("tests/test_example.py", "w") as f: - f.write("""from src.example import add + f.write(f"""from {project_module_name}.example import add def test_add(): assert add(1, 2) == 3 @@ -103,17 +165,29 @@ def test_add(): elif code_choice == "3": print("๐Ÿงน Removing all example code...") run_command("make clean-example") + # Create __init__.py in tests + with open("tests/__init__.py", "w") as f: + f.write("") else: - print("๐Ÿ“š Keeping example code for reference...") + print("๐Ÿ“š Updating example code imports for new module name...") + # Update example.py to use new module name + if os.path.exists("src/example.py"): + with open("src/example.py", "r") as f: + example_content = f.read() + # Save it to new module directory + with open(f"{project_module_name}/example.py", "w") as f: + f.write(example_content) + + # Update test imports + if os.path.exists("tests/test_example.py"): + with open("tests/test_example.py", "r") as f: + test_content = f.read() + updated_test = test_content.replace("from src.", f"from {project_module_name}.") + with open("tests/test_example.py", "w") as f: + f.write(updated_test) - # Update pyproject.toml - print("๐Ÿ“ Updating project configuration...") - update_pyproject_toml( - project_name, - project_description, - author_name, - author_email - ) + # Update already happened above, fix the duplicate + # The configuration has already been updated above # Get current directory name and handle renaming current_dir = os.path.basename(os.getcwd()) @@ -124,6 +198,18 @@ def test_add(): if os.path.exists(new_dir): print(f"โš ๏ธ Directory {project_name} already exists. Keeping current directory name.") else: + # Update source code directory references in Makefile + makefile_path = Path("Makefile") + with open(makefile_path, "r") as f: + makefile_content = f.read() + + # Replace any hardcoded references to python-collab-template in the Makefile + updated_makefile = makefile_content.replace("python-collab-template", project_name) + + with open(makefile_path, "w") as f: + f.write(updated_makefile) + + # Now rename the directory os.chdir(parent_dir) os.rename(current_dir, project_name) os.chdir(project_name) @@ -155,9 +241,9 @@ def test_add(): else: print("โฉ Skipping pre-commit hooks setup") - # Initial commit + # Initial commit without running pre-commit hooks run_command("git add .") - run_command('git commit -m "feat: Initial project setup"') + run_command('git commit -m "feat: Initial project setup" --no-verify') print("โœจ Project initialized successfully!") print("""