Skip to content

Commit 65f36e7

Browse files
authored
Feature/uv (#1)
* added packaged manger option, and added tests to make sure reqs files are removed * updated makes for uv option * updated toml for uv vs pip * Fixed Makefile for uc build command
1 parent e5047af commit 65f36e7

File tree

7 files changed

+328
-20
lines changed

7 files changed

+328
-20
lines changed

cookiecutter.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
"3.12",
2323
"3.13"
2424
],
25+
"package_manager": [
26+
"uv",
27+
"pip"
28+
],
2529
"use_requests": [
2630
"n",
2731
"y"
@@ -43,7 +47,7 @@
4347
"https"
4448
],
4549
"__template_repo": "https://github.com/btr1975/cookiecutter-python-fastapi-openapi",
46-
"__template_version": "1.0.13",
50+
"__template_version": "2.0.0",
4751
"_new_lines": "\n",
4852
"_copy_without_render": [
4953
"{{cookiecutter.__app_name}}/templates",
@@ -75,6 +79,11 @@
7579
"3.12": "3.12",
7680
"3.13": "3.13"
7781
},
82+
"package_manager": {
83+
"__prompt__": "Which pacakge manager for Python will be supported",
84+
"uv": "UV By Astral",
85+
"pip": "PIP (The built in Python Package Installer)"
86+
},
7887
"use_requests": {
7988
"__prompt__": "Will you use the requests library",
8089
"n": "No",

hooks/post_gen_project.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
'{% if cookiecutter.container_runtime == "docker" %}containers/Containerfile{% endif %}',
2121
]
2222

23+
REMOVE_PATHS_UV = [
24+
'{% if cookiecutter.package_manager == "uv" %}requirements.txt{% endif %}',
25+
'{% if cookiecutter.package_manager == "uv" %}requirements-dev.txt{% endif %}',
26+
]
27+
2328

2429
def remove_paths(paths_to_remove: List[str]) -> None:
2530
"""Remove files and directories
@@ -41,3 +46,4 @@ def remove_paths(paths_to_remove: List[str]) -> None:
4146
remove_paths(REMOVE_PATHS_NO_WEBPAGES)
4247
remove_paths(REMOVE_PATHS_PODMAN)
4348
remove_paths(REMOVE_PATHS_DOCKER)
49+
remove_paths(REMOVE_PATHS_UV)

tests/conftest.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ def bake_project_api_only_podman() -> dict:
1414
"email": "name@example.com",
1515
"git_username": "some-username",
1616
"git_url": "https://github.com/some-username/python-with-cli",
17+
"package_manager": "pip",
18+
}
19+
20+
return options
21+
22+
23+
@pytest.fixture
24+
def bake_project_uv_api_only_podman() -> dict:
25+
options = {
26+
"git_repo_name": "api-only",
27+
"include_webpages": "n",
28+
"email": "name@example.com",
29+
"git_username": "some-username",
30+
"git_url": "https://github.com/some-username/python-with-cli",
31+
"package_manager": "uv",
1732
}
1833

1934
return options
@@ -28,6 +43,22 @@ def bake_project_api_only_docker() -> dict:
2843
"git_username": "some-username",
2944
"git_url": "https://github.com/some-username/python-with-cli",
3045
"container_runtime": "docker",
46+
"package_manager": "pip",
47+
}
48+
49+
return options
50+
51+
52+
@pytest.fixture
53+
def bake_project_uv_api_only_docker() -> dict:
54+
options = {
55+
"git_repo_name": "api-only",
56+
"include_webpages": "n",
57+
"email": "name@example.com",
58+
"git_username": "some-username",
59+
"git_url": "https://github.com/some-username/python-with-cli",
60+
"container_runtime": "docker",
61+
"package_manager": "uv",
3162
}
3263

3364
return options
@@ -41,6 +72,21 @@ def bake_project_api_with_webpages_podman() -> dict:
4172
"email": "name@example.com",
4273
"git_username": "some-username",
4374
"git_url": "https://github.com/some-username/python-with-cli",
75+
"package_manager": "pip",
76+
}
77+
78+
return options
79+
80+
81+
@pytest.fixture
82+
def bake_project_uv_api_with_webpages_podman() -> dict:
83+
options = {
84+
"git_repo_name": "api-with-webpages",
85+
"include_webpages": "y",
86+
"email": "name@example.com",
87+
"git_username": "some-username",
88+
"git_url": "https://github.com/some-username/python-with-cli",
89+
"package_manager": "uv",
4490
}
4591

4692
return options
@@ -55,6 +101,22 @@ def bake_project_api_with_webpages_docker() -> dict:
55101
"git_username": "some-username",
56102
"git_url": "https://github.com/some-username/python-with-cli",
57103
"container_runtime": "docker",
104+
"package_manager": "pip",
105+
}
106+
107+
return options
108+
109+
110+
@pytest.fixture
111+
def bake_project_uv_api_with_webpages_docker() -> dict:
112+
options = {
113+
"git_repo_name": "api-with-webpages",
114+
"include_webpages": "y",
115+
"email": "name@example.com",
116+
"git_username": "some-username",
117+
"git_url": "https://github.com/some-username/python-with-cli",
118+
"container_runtime": "docker",
119+
"package_manager": "uv",
58120
}
59121

60122
return options

tests/test_cookies.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,27 @@ def test_bake_project_api_only_podman(cookies, bake_project_api_only_podman):
66
assert result.project_path.name == "api-only"
77
assert result.project_path.is_dir()
88
assert result.project_path.joinpath("README.md").is_file()
9+
assert result.project_path.joinpath("requirements.txt").is_file()
10+
assert result.project_path.joinpath("requirements-dev.txt").is_file()
11+
assert result.project_path.joinpath("api_only").is_dir()
12+
assert result.project_path.joinpath("containers").is_dir()
13+
assert result.project_path.joinpath("containers", "Containerfile").is_file()
14+
assert not result.project_path.joinpath("containers", "Dockerfile").is_file()
15+
assert not result.project_path.joinpath("api_only", "static").is_dir()
16+
assert not result.project_path.joinpath("api_only", "templates").is_dir()
17+
assert not result.project_path.joinpath("api_only", "routers", "hello_world.py").is_file()
18+
19+
20+
def test_bake_project_uv_api_only_podman(cookies, bake_project_uv_api_only_podman):
21+
result = cookies.bake(extra_context=bake_project_uv_api_only_podman)
22+
23+
assert result.exit_code == 0
24+
assert result.exception is None
25+
assert result.project_path.name == "api-only"
26+
assert result.project_path.is_dir()
27+
assert result.project_path.joinpath("README.md").is_file()
28+
assert not result.project_path.joinpath("requirements.txt").is_file()
29+
assert not result.project_path.joinpath("requirements-dev.txt").is_file()
930
assert result.project_path.joinpath("api_only").is_dir()
1031
assert result.project_path.joinpath("containers").is_dir()
1132
assert result.project_path.joinpath("containers", "Containerfile").is_file()
@@ -23,6 +44,27 @@ def test_bake_project_api_only_docker(cookies, bake_project_api_only_docker):
2344
assert result.project_path.name == "api-only"
2445
assert result.project_path.is_dir()
2546
assert result.project_path.joinpath("README.md").is_file()
47+
assert result.project_path.joinpath("requirements.txt").is_file()
48+
assert result.project_path.joinpath("requirements-dev.txt").is_file()
49+
assert result.project_path.joinpath("api_only").is_dir()
50+
assert result.project_path.joinpath("containers").is_dir()
51+
assert not result.project_path.joinpath("containers", "Containerfile").is_file()
52+
assert result.project_path.joinpath("containers", "Dockerfile").is_file()
53+
assert not result.project_path.joinpath("api_only", "static").is_dir()
54+
assert not result.project_path.joinpath("api_only", "templates").is_dir()
55+
assert not result.project_path.joinpath("api_only", "routers", "hello_world.py").is_file()
56+
57+
58+
def test_bake_project_uv_api_only_docker(cookies, bake_project_uv_api_only_docker):
59+
result = cookies.bake(extra_context=bake_project_uv_api_only_docker)
60+
61+
assert result.exit_code == 0
62+
assert result.exception is None
63+
assert result.project_path.name == "api-only"
64+
assert result.project_path.is_dir()
65+
assert result.project_path.joinpath("README.md").is_file()
66+
assert not result.project_path.joinpath("requirements.txt").is_file()
67+
assert not result.project_path.joinpath("requirements-dev.txt").is_file()
2668
assert result.project_path.joinpath("api_only").is_dir()
2769
assert result.project_path.joinpath("containers").is_dir()
2870
assert not result.project_path.joinpath("containers", "Containerfile").is_file()
@@ -40,6 +82,27 @@ def test_bake_project_api_with_webpages_podman(cookies, bake_project_api_with_we
4082
assert result.project_path.name == "api-with-webpages"
4183
assert result.project_path.is_dir()
4284
assert result.project_path.joinpath("README.md").is_file()
85+
assert result.project_path.joinpath("requirements.txt").is_file()
86+
assert result.project_path.joinpath("requirements-dev.txt").is_file()
87+
assert result.project_path.joinpath("api_with_webpages").is_dir()
88+
assert result.project_path.joinpath("containers").is_dir()
89+
assert result.project_path.joinpath("containers", "Containerfile").is_file()
90+
assert not result.project_path.joinpath("containers", "Dockerfile").is_file()
91+
assert result.project_path.joinpath("api_with_webpages", "static").is_dir()
92+
assert result.project_path.joinpath("api_with_webpages", "templates").is_dir()
93+
assert result.project_path.joinpath("api_with_webpages", "routers", "hello_world.py").is_file()
94+
95+
96+
def test_bake_project_uv_api_with_webpages_podman(cookies, bake_project_uv_api_with_webpages_podman):
97+
result = cookies.bake(extra_context=bake_project_uv_api_with_webpages_podman)
98+
99+
assert result.exit_code == 0
100+
assert result.exception is None
101+
assert result.project_path.name == "api-with-webpages"
102+
assert result.project_path.is_dir()
103+
assert result.project_path.joinpath("README.md").is_file()
104+
assert not result.project_path.joinpath("requirements.txt").is_file()
105+
assert not result.project_path.joinpath("requirements-dev.txt").is_file()
43106
assert result.project_path.joinpath("api_with_webpages").is_dir()
44107
assert result.project_path.joinpath("containers").is_dir()
45108
assert result.project_path.joinpath("containers", "Containerfile").is_file()
@@ -57,6 +120,27 @@ def test_bake_project_api_with_webpages_docker(cookies, bake_project_api_with_we
57120
assert result.project_path.name == "api-with-webpages"
58121
assert result.project_path.is_dir()
59122
assert result.project_path.joinpath("README.md").is_file()
123+
assert result.project_path.joinpath("requirements.txt").is_file()
124+
assert result.project_path.joinpath("requirements-dev.txt").is_file()
125+
assert result.project_path.joinpath("api_with_webpages").is_dir()
126+
assert result.project_path.joinpath("containers").is_dir()
127+
assert not result.project_path.joinpath("containers", "Containerfile").is_file()
128+
assert result.project_path.joinpath("containers", "Dockerfile").is_file()
129+
assert result.project_path.joinpath("api_with_webpages", "static").is_dir()
130+
assert result.project_path.joinpath("api_with_webpages", "templates").is_dir()
131+
assert result.project_path.joinpath("api_with_webpages", "routers", "hello_world.py").is_file()
132+
133+
134+
def test_bake_project_uv_api_with_webpages_docker(cookies, bake_project_uv_api_with_webpages_docker):
135+
result = cookies.bake(extra_context=bake_project_uv_api_with_webpages_docker)
136+
137+
assert result.exit_code == 0
138+
assert result.exception is None
139+
assert result.project_path.name == "api-with-webpages"
140+
assert result.project_path.is_dir()
141+
assert result.project_path.joinpath("README.md").is_file()
142+
assert not result.project_path.joinpath("requirements.txt").is_file()
143+
assert not result.project_path.joinpath("requirements-dev.txt").is_file()
60144
assert result.project_path.joinpath("api_with_webpages").is_dir()
61145
assert result.project_path.joinpath("containers").is_dir()
62146
assert not result.project_path.joinpath("containers", "Containerfile").is_file()

{{cookiecutter.git_repo_name}}/Makefile

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Makefile for project needs
22
# Author: Ben Trachtenberg
3-
# Version: 1.0.8
3+
# Version: 2.0.0
44
#
55

66
.PHONY: all info build build-container coverage format pylint pytest gh-pages build dev-run start-container \
7-
stop-container remove-container check-vuln check-security
7+
stop-container remove-container check-vuln check-security pip-export
88

99
info:
1010
@echo "make options"
@@ -21,23 +21,16 @@ info:
2121
@echo " start-container To start the container"
2222
@echo " stop-container To stop the container"
2323
@echo " remove-container To remove the container"
24+
{% if cookiecutter.package_manager == 'uv' %} @echo " pip-export To export the requirements to requirements.txt and requirements-dev.txt"{% endif %}
2425
{% if cookiecutter.app_documents_location == 'github-pages' %} @echo " gh-pages To create the GitHub pages"{% endif %}
2526

27+
{% if cookiecutter.package_manager == 'pip' %}
28+
2629
all: format pylint coverage check-security check-vuln
2730

2831
build:
2932
@python -m build
3033

31-
{% if cookiecutter.container_runtime == "podman" %}
32-
build-container:
33-
@cd containers && podman build --ssh=default --build-arg=build_branch=main -t {{ cookiecutter.git_repo_name }}:latest -f Containerfile
34-
{% endif %}
35-
36-
{% if cookiecutter.container_runtime == "docker" %}
37-
build-container:
38-
@cd containers && docker build --ssh=default --build-arg=build_branch=main -t {{ cookiecutter.git_repo_name }}:latest -f Dockerfile
39-
{% endif %}
40-
4134
coverage:
4235
@pytest --cov --cov-report=html -vvv
4336

@@ -54,14 +47,62 @@ pytest:
5447
dev-run:
5548
@python -c "from {{cookiecutter.__app_name}} import cli;cli()" start -p 8080 -r
5649

50+
check-vuln:
51+
@pip-audit -r requirements.txt
52+
53+
check-security:
54+
@bandit -c pyproject.toml -r .
55+
5756
{% if cookiecutter.app_documents_location == 'github-pages' %}
5857
gh-pages:
5958
@rm -rf ./docs/source/code
6059
@sphinx-apidoc -o ./docs/source/code ./{{cookiecutter.__app_name}}
6160
@sphinx-build ./docs ./docs/gh-pages
6261
{% endif %}
6362

63+
{% elif cookiecutter.package_manager == 'uv' %}
64+
65+
all: format pylint coverage check-security pip-export
66+
67+
build:
68+
@uv build --wheel --sdist
69+
70+
coverage:
71+
@uv run pytest --cov --cov-report=html -vvv
72+
73+
format:
74+
@uv run black {{cookiecutter.__app_name}}/
75+
@uv run black tests/
76+
77+
pylint:
78+
@uv run pylint {{cookiecutter.__app_name}}/
79+
80+
pytest:
81+
@uv run pytest --cov -vvv
82+
83+
dev-run:
84+
@uv run python -c "from {{cookiecutter.__app_name}} import cli;cli()" start -p 8080 -r
85+
86+
check-security:
87+
@uv run bandit -c pyproject.toml -r .
88+
89+
pip-export:
90+
@uv export --no-dev --no-emit-project --no-editable > requirements.txt
91+
@uv export --no-emit-project --no-editable > requirements-dev.txt
92+
93+
{% if cookiecutter.app_documents_location == 'github-pages' %}
94+
gh-pages:
95+
@rm -rf ./docs/source/code
96+
@uv run sphinx-apidoc -o ./docs/source/code ./{{cookiecutter.__app_name}}
97+
@uv run sphinx-build ./docs ./docs/gh-pages
98+
{% endif %}
99+
100+
{% endif %}
101+
64102
{% if cookiecutter.container_runtime == "podman" %}
103+
build-container:
104+
@cd containers && podman build --ssh=default --build-arg=build_branch=main -t {{ cookiecutter.git_repo_name }}:latest -f Containerfile
105+
65106
start-container:
66107
@podman run -itd --name {{ cookiecutter.git_repo_name }} -p 8080:8080 localhost/{{ cookiecutter.git_repo_name }}:latest
67108

@@ -73,6 +114,9 @@ remove-container:
73114
{% endif %}
74115

75116
{% if cookiecutter.container_runtime == "docker" %}
117+
build-container:
118+
@cd containers && docker build --ssh=default --build-arg=build_branch=main -t {{ cookiecutter.git_repo_name }}:latest -f Dockerfile
119+
76120
start-container:
77121
@docker run -itd --name {{ cookiecutter.git_repo_name }} -p 8080:8080 localhost/{{ cookiecutter.git_repo_name }}:latest
78122

@@ -82,9 +126,3 @@ stop-container:
82126
remove-container:
83127
@docker rm {{ cookiecutter.git_repo_name }}
84128
{% endif %}
85-
86-
check-vuln:
87-
@pip-audit -r requirements.txt
88-
89-
check-security:
90-
@bandit -c pyproject.toml -r .

0 commit comments

Comments
 (0)