-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy path_init.py
More file actions
178 lines (152 loc) · 5.79 KB
/
_init.py
File metadata and controls
178 lines (152 loc) · 5.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
"""Project initialization and build system setup."""
import re
from typing_extensions import assert_never
from usethis._backend.dispatch import get_backend
from usethis._backend.poetry.init import (
ensure_pyproject_toml_via_poetry,
opinionated_poetry_init,
)
from usethis._backend.uv.init import (
ensure_pyproject_toml_via_uv,
opinionated_uv_init,
)
from usethis._config import usethis_config
from usethis._console import tick_print
from usethis._deps import get_project_deps
from usethis._fallback import (
FALLBACK_HATCHLING_VERSION,
FALLBACK_UV_VERSION,
next_breaking_version,
)
from usethis._file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.project.name import get_project_name
from usethis._types.backend import BackendEnum
from usethis._types.build_backend import BuildBackendEnum
_BUILD_SYSTEM_CONFIG: dict[BuildBackendEnum, tuple[list[str], str]] = {
BuildBackendEnum.hatch: (
[
f"hatchling>={FALLBACK_HATCHLING_VERSION},<{next_breaking_version(FALLBACK_HATCHLING_VERSION)}"
],
"hatchling.build",
),
BuildBackendEnum.uv: (
[
f"uv_build>={FALLBACK_UV_VERSION},<{next_breaking_version(FALLBACK_UV_VERSION)}"
],
"uv_build",
),
}
def project_init():
"""Initialize the project by creating the pyproject.toml and project structure."""
if (usethis_config.cpd() / "pyproject.toml").exists():
return
tick_print("Writing 'pyproject.toml' and initializing project.")
backend = get_backend()
if backend is BackendEnum.uv:
opinionated_uv_init()
elif backend is BackendEnum.poetry:
opinionated_poetry_init()
_create_project_structure()
elif backend is BackendEnum.none:
# pyproject.toml
with usethis_config.set(instruct_only=True):
ensure_pyproject_toml()
_create_project_structure()
else:
assert_never(backend)
def _create_project_structure() -> None:
"""Create the standard project structure files.
Creates README.md, src/<package_name>/__init__.py, and
src/<package_name>/py.typed if they don't already exist.
"""
# README.md
(usethis_config.cpd() / "README.md").touch(exist_ok=True)
# src/
src_dir = usethis_config.cpd() / "src"
src_dir.mkdir(exist_ok=True)
project_name = get_project_name()
pkg_name = _regularize_package_name(project_name)
(src_dir / pkg_name).mkdir(exist_ok=True)
init_path = src_dir / pkg_name / "__init__.py"
if not init_path.exists():
init_path.write_text(
f"""\
def hello() -> str:
return "Hello from {project_name}!"
""",
encoding="utf-8",
)
(src_dir / pkg_name / "py.typed").touch(exist_ok=True)
def _regularize_package_name(project_name: str) -> str:
"""Regularize the package name to be suitable for Python packaging."""
# https://peps.python.org/pep-0008/#package-and-module-names
# Replace non-alphanumeric characters with underscores
# Conjoin consecutive underscores
# Add leading underscore if it starts with a digit
project_name = re.sub(r"\W+", "_", project_name)
project_name = re.sub(r"_+", "_", project_name)
if project_name[0].isdigit():
project_name = "_" + project_name
return project_name.lower()
def write_simple_requirements_txt(*, output_file: str = "requirements.txt") -> None:
r"""Write a simple requirements.txt file with -e . and any project dependencies.
This is used when we don't have a lock file or when using backend=none.
Always writes at least "-e .\n", and appends any dependencies found in
pyproject.toml if they exist.
"""
tick_print(f"Writing '{output_file}'.")
path = usethis_config.cpd() / output_file
with open(path, "w", encoding="utf-8") as f:
# Always write -e . first
f.write("-e .\n")
# Add any dependencies that exist
project_deps = get_project_deps()
if project_deps:
f.writelines(dep.to_requirement_string() + "\n" for dep in project_deps)
def ensure_dep_declaration_file() -> None:
"""Ensure that the file where dependencies are declared exists, if necessary."""
backend = get_backend()
if backend is BackendEnum.uv or backend is BackendEnum.poetry:
ensure_pyproject_toml()
elif backend is BackendEnum.none:
# No dependencies are interacted with; we just display messages.
pass
else:
assert_never(backend)
def ensure_pyproject_toml(*, author: bool = True) -> None:
"""Ensure that a pyproject.toml file exists, creating it if necessary."""
if (usethis_config.cpd() / "pyproject.toml").exists():
return
tick_print("Writing 'pyproject.toml'.")
backend = get_backend()
build_backend = usethis_config.build_backend
if backend is BackendEnum.uv:
ensure_pyproject_toml_via_uv(author=author)
elif backend is BackendEnum.poetry:
ensure_pyproject_toml_via_poetry(author=author)
elif backend is BackendEnum.none:
requires, build_backend_str = _BUILD_SYSTEM_CONFIG[build_backend]
requires_str = ", ".join(f'"{r}"' for r in requires)
(usethis_config.cpd() / "pyproject.toml").write_text(
f"""\
[project]
name = "{get_project_name()}"
version = "0.1.0"
dependencies = []
[build-system]
requires = [{requires_str}]
build-backend = "{build_backend_str}"
""",
encoding="utf-8",
)
else:
assert_never(backend)
if build_backend is BuildBackendEnum.hatch and not (
(usethis_config.cpd() / "src").exists()
and (usethis_config.cpd() / "src").is_dir()
):
# hatch needs to know where to find the package
PyprojectTOMLManager().set_value(
keys=["tool", "hatch", "build", "targets", "wheel", "packages"],
value=["."],
)