Skip to content

Commit

Permalink
Support Python packages with no namespace or multiple namespaces (#86)
Browse files Browse the repository at this point in the history
* Support Python packages with no namespace or multiple namespaces

* Use correct branch of cookieplone

* Add tests

* Require cookieplone 0.8.0

---------

Co-authored-by: Érico Andrei <[email protected]>
  • Loading branch information
davisagli and ericof authored Nov 13, 2024
1 parent 978355f commit 560ee7a
Show file tree
Hide file tree
Showing 79 changed files with 156 additions and 113 deletions.
8 changes: 4 additions & 4 deletions backend_addon/cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
"feature_headless": ["1", "0"],
"__feature_headless": "{{ cookiecutter.feature_headless }}",
"__feature_distribution": "0",
"__package_name": "{{ cookiecutter.python_package_name | package_name }}",
"__package_namespace": "{{ cookiecutter.python_package_name | package_namespace }}",
"__package_namespaces": "{{ cookiecutter.python_package_name | package_namespaces }}",
"__package_path": "{{ cookiecutter.python_package_name | package_path }}",
"__folder_name": "{{ cookiecutter.python_package_name }}",
"__python_package_name_upper": "{{ cookiecutter.python_package_name | pascal_case }}",
"__profile_language": "en",
Expand Down Expand Up @@ -41,8 +41,8 @@
"cookieplone.filters.latest_plone",
"cookieplone.filters.use_prerelease_versions",
"cookieplone.filters.pascal_case",
"cookieplone.filters.package_name",
"cookieplone.filters.package_namespace"
"cookieplone.filters.package_namespaces",
"cookieplone.filters.package_path"
],
"__cookieplone_repository_path": "",
"__cookieplone_template": ""
Expand Down
38 changes: 26 additions & 12 deletions backend_addon/hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"""Post generation hook."""

import os
from collections import OrderedDict
from copy import deepcopy
from pathlib import Path

from cookieplone.settings import QUIET_MODE_VAR
from cookieplone.utils import console, files, git, plone

context: OrderedDict = {{cookiecutter}}
Expand All @@ -18,12 +20,20 @@


def handle_feature_headless(context: OrderedDict, output_dir: Path):
package_namespace = context.get("__package_namespace")
package_name = context.get("__package_name")
output_dir = output_dir / "src" / package_namespace / package_name
output_dir = output_dir / "src" / "packagename"
files.remove_files(output_dir, FEATURES_TO_REMOVE["feature_headless"])


def handle_create_namespace_packages(context: OrderedDict, output_dir: Path):
plone.create_namespace_packages(
output_dir / "src/packagename", context["python_package_name"]
)


def handle_format(context: OrderedDict, output_dir: Path):
plone.format_python_codebase(output_dir)


def handle_git_initialization(context: OrderedDict, output_dir: Path):
"""Initialize a GIT repository for the project codebase."""
git.initialize_repository(output_dir)
Expand All @@ -32,9 +42,11 @@ def handle_git_initialization(context: OrderedDict, output_dir: Path):
def main():
"""Final fixes."""
output_dir = Path().cwd()
is_subtemplate = os.environ.get(QUIET_MODE_VAR) == "1"
remove_headless = not int(
context.get("feature_headless")
) # {{ cookiecutter.__feature_headless }}
create_namespace_packages = not is_subtemplate
initialize_git = bool(
int(context.get("__backend_addon_git_initialize"))
) # {{ cookiecutter.__backend_addon_git_initialize }}
Expand All @@ -48,6 +60,16 @@ def main():
"Remove files used in headless setup",
remove_headless,
],
[
handle_create_namespace_packages,
"Create namespace packages",
create_namespace_packages,
],
[
handle_format,
"Format code",
backend_format,
],
[
handle_git_initialization,
"Initialize Git repository",
Expand All @@ -61,18 +83,10 @@ def main():
console.print(f" -> {title}")
func(new_context, output_dir)

# Run format
if backend_format:
plone.format_python_codebase(output_dir)

msg = """
[bold blue]{{ cookiecutter.title }}[/bold blue]
Now, enter the repository run the code formatter with:
make format
start coding, and push to your organization.
Now, enter the repository, start coding, and push to your organization.
Sorry for the convenience,
The Plone Community.
Expand Down
20 changes: 14 additions & 6 deletions backend_addon/hooks/pre_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import sys

try:
from cookieplone import __version__ as cookieplone_version
from cookieplone import data
from cookieplone.utils import commands, console, sanity

HAS_COOKIEPLONE = True
except ModuleNotFoundError:
HAS_COOKIEPLONE = False
print("This template should be run with cookieplone")
sys.exit(1)
from packaging.version import Version


SUPPORTED_PYTHON_VERSIONS = [
Expand All @@ -23,6 +24,16 @@
def sanity_check() -> data.SanityCheckResults:
"""Run sanity checks on the system."""
checks = [
data.SanityCheck(
"Cookieplone",
lambda: (
""
if Version(cookieplone_version) > Version("0.8.0.dev0")
else "This template requires Cookieplone 0.8 or higher."
),
[],
"error",
),
data.SanityCheck(
"Python",
commands.check_python_version,
Expand All @@ -36,9 +47,6 @@ def sanity_check() -> data.SanityCheckResults:

def main():
"""Validate context."""
if not HAS_COOKIEPLONE:
print("This template should be run with cookieplone")
sys.exit(1)

msg = """
Creating a new Plone Addon
Expand Down
50 changes: 43 additions & 7 deletions backend_addon/tests/test_cutter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Test cookiecutter generation with all features enabled."""

from copy import deepcopy
from pathlib import Path

import pytest
Expand Down Expand Up @@ -40,10 +41,8 @@ def test_root_files_generated(cutter_result, file_path):
@pytest.mark.parametrize("file_path", PKG_SRC_FILES)
def test_pkg_src_files_generated(cutter_result, file_path: str):
"""Check if distribution files were generated."""
package_namespace = cutter_result.context["__package_namespace"]
package_name = cutter_result.context["__package_name"]
file_path = file_path.format(package_name=package_name)
src_path = cutter_result.project_path / "src" / package_namespace / package_name
package_path = cutter_result.context["__package_path"]
src_path = cutter_result.project_path / "src" / package_path
path = src_path / file_path
assert path.exists()
assert path.is_file()
Expand All @@ -52,9 +51,8 @@ def test_pkg_src_files_generated(cutter_result, file_path: str):
@pytest.mark.parametrize("file_path", PKG_SRC_FEATURE_HEADLESS)
def test_pkg_src_feature_files_generated(cutter_result, file_path: str):
"""Check if feature-specific files were generated."""
package_namespace = cutter_result.context["__package_namespace"]
package_name = cutter_result.context["__package_name"]
src_path = cutter_result.project_path / "src" / package_namespace / package_name
package_path = cutter_result.context["__package_path"]
src_path = cutter_result.project_path / "src" / package_path
path = src_path / file_path
assert path.exists()
assert path.is_file()
Expand Down Expand Up @@ -89,3 +87,41 @@ def test_git_initialization_not_set(cookies, context_no_git):
cutter_result = cookies.bake(extra_context=context_no_git)
path = cutter_result.project_path
assert git.check_path_is_repository(path) is False


@pytest.fixture(scope="session")
def cutter_result_no_namespace(context, cookies_session) -> dict:
"""Cookiecutter context without namespace package."""
new_context = deepcopy(context)
new_context["python_package_name"] = "addon"
return cookies_session.bake(extra_context=new_context)


@pytest.fixture(scope="session")
def cutter_result_two_namespaces(context, cookies_session) -> dict:
"""Cookiecutter context with 2 namespace packages."""
new_context = deepcopy(context)
new_context["python_package_name"] = "foo.bar.baz"
return cookies_session.bake(extra_context=new_context)


@pytest.mark.parametrize("file_path", PKG_SRC_FILES)
def test_pkg_src_files_generated_without_namespace(
cutter_result_no_namespace, file_path: str
):
"""Check package contents with no namespaces."""
src_path = cutter_result_no_namespace.project_path / "src/addon"
path = src_path / file_path
assert path.exists()
assert path.is_file()


@pytest.mark.parametrize("file_path", PKG_SRC_FILES)
def test_pkg_src_files_generated_with_two_namespaces(
cutter_result_two_namespaces, file_path: str
):
"""Check package contents with 2 namespaces."""
src_path = cutter_result_two_namespaces.project_path / "src/foo/bar/baz"
path = src_path / file_path
assert path.exists()
assert path.is_file()
11 changes: 4 additions & 7 deletions backend_addon/tests/test_cutter_no_headless.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,8 @@ def test_root_files_generated(cutter_result, file_path):
@pytest.mark.parametrize("file_path", PKG_SRC_FILES)
def test_pkg_src_files_generated(cutter_result, file_path: str):
"""Check if distribution files were generated."""
package_namespace = cutter_result.context["__package_namespace"]
package_name = cutter_result.context["__package_name"]
file_path = file_path.format(package_name=package_name)
src_path = cutter_result.project_path / "src" / package_namespace / package_name
package_path = cutter_result.context["__package_path"]
src_path = cutter_result.project_path / "src" / package_path
path = src_path / file_path
assert path.exists()
assert path.is_file()
Expand All @@ -56,8 +54,7 @@ def test_pkg_src_files_generated(cutter_result, file_path: str):
@pytest.mark.parametrize("file_path", PKG_SRC_FEATURE_HEADLESS)
def test_pkg_src_headless_files_not_generated(cutter_result, file_path: str):
"""Check feature-specific files were not generated."""
package_namespace = cutter_result.context["__package_namespace"]
package_name = cutter_result.context["__package_name"]
src_path = cutter_result.project_path / "src" / package_namespace / package_name
package_path = cutter_result.context["__package_path"]
src_path = cutter_result.project_path / "src" / package_path
path = src_path / file_path
assert path.exists() is False
2 changes: 1 addition & 1 deletion backend_addon/{{ cookiecutter.__folder_name }}/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
graft src/{{ cookiecutter.__package_namespace }}
graft src/{{ cookiecutter.__package_path }}
graft docs
graft news
graft tests
Expand Down
2 changes: 1 addition & 1 deletion backend_addon/{{ cookiecutter.__folder_name }}/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
},
license="GPL version 2",
packages=find_packages("src", exclude=["ez_setup"]),
namespace_packages=["{{ cookiecutter.__package_namespace }}"],
namespace_packages=[{{ cookiecutter.__package_namespaces }}],
package_dir={"": "src"},
include_package_data=True,
zip_safe=False,
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion frontend_addon/tests/test_cutter.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ def test_root_files_generated(cutter_result, file_path):
def test_pkg_src_files_generated(cutter_result, file_path: str):
"""Check if package files were generated."""
package_name = cutter_result.context["frontend_addon_name"]
file_path = file_path.format(package_name=package_name)
src_path = cutter_result.project_path / "packages" / package_name
path = src_path / file_path
assert path.exists()
Expand Down
24 changes: 5 additions & 19 deletions project/cookiecutter.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,10 @@
"frontend_addon_name": "volto-{{ cookiecutter.python_package_name|replace('_', '-')|replace('.', '-') }}",
"language_code": ["en", "de", "es", "pt-br", "nl", "fi"],
"github_organization": "collective",
"container_registry": [
"github",
"docker_hub",
"gitlab"
],
"devops_cache": [
"1",
"0"
],
"devops_ansible": [
"1",
"0"
],
"devops_gha_deploy": [
"1",
"0"
],
"container_registry": ["github", "docker_hub", "gitlab"],
"devops_cache": ["1", "0"],
"devops_ansible": ["1", "0"],
"devops_gha_deploy": ["1", "0"],
"__feature_headless": "1",
"__npm_package_name": "{{ cookiecutter.frontend_addon_name }}",
"__folder_name": "{{ cookiecutter.project_slug }}",
Expand Down Expand Up @@ -67,8 +54,8 @@
"__devops_varnish_version": "7.6",
"__devops_db_version": "14",
"__devops_db_password": "{{ random_ascii_string(12) }}",
"__backend_addon_format": "1",
"__backend_addon_git_initialize": "0",
"__backend_addon_format": "1",
"__project_git_initialize": "1",
"__prompts__": {
"title": "Project Title",
Expand Down Expand Up @@ -120,7 +107,6 @@
"devops/requirements",
"devops/tasks",
"devops/inventory/group_vars/all/users.yml"

],
"_extensions": [
"cookieplone.filters.use_prerelease_versions",
Expand Down
5 changes: 5 additions & 0 deletions project/hooks/post_gen_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ def main():
console.print(f" -> {title}")
func(new_context, output_dir)

# Create namespace packages
plone.create_namespace_packages(
output_dir / "backend/src/packagename", context["python_package_name"]
)

# Run format
if backend_format:
backend_folder = output_dir / "backend"
Expand Down
25 changes: 18 additions & 7 deletions project/hooks/pre_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import sys

from cookieplone import data
from cookieplone.utils import commands, console, sanity

HAS_COOKIEPLONE = True
try:
from cookieplone import __version__ as cookieplone_version
from cookieplone import data
from cookieplone.utils import commands, console, sanity
except ModuleNotFoundError:
print("This template should be run with cookieplone")
sys.exit(1)
from packaging.version import Version

SUPPORTED_PYTHON_VERSIONS = [
"3.8",
Expand All @@ -19,6 +23,16 @@
def sanity_check() -> data.SanityCheckResults:
"""Run sanity checks on the system."""
checks = [
data.SanityCheck(
"Cookieplone",
lambda: (
""
if Version(cookieplone_version) > Version("0.8.0.dev0")
else "This template requires Cookieplone 0.8 or higher."
),
[],
"error",
),
data.SanityCheck(
"Python",
commands.check_python_version,
Expand All @@ -41,9 +55,6 @@ def sanity_check() -> data.SanityCheckResults:

def main():
"""Validate context."""
if not HAS_COOKIEPLONE:
print("This template should be run with cookieplone")
sys.exit(1)

msg = """
Creating a new Plone Project
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ isort
pytest
pytest-cookies
pytest-jsonschema >= 1.0.0a2
cookieplone>=0.7.0
cookieplone >= 0.8.0
GitPython
wheel
Loading

0 comments on commit 560ee7a

Please sign in to comment.