Skip to content

Commit 97d5589

Browse files
Merge branch '1000-stop-using-pytest-emoji' of https://github.com/nathanjmcdougall/usethis-python into 1000-stop-using-pytest-emoji
2 parents 2906bdc + 68252e0 commit 97d5589

50 files changed

Lines changed: 1254 additions & 354 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,14 @@ jobs:
4343
with:
4444
version: ${{ matrix.uv_version || 'latest' }}
4545
enable-cache: true
46-
47-
- name: Set up Python
48-
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
49-
with:
5046
python-version: ${{ matrix.python_version }}
5147

52-
- name: Bump dependencies
53-
if: ${{ matrix.dependencies == 'max' }}
54-
run: |
55-
uv sync --upgrade
56-
5748
- name: Setup dependencies
5849
run: |
59-
uv python pin $CI_PYTHON_VERSION
60-
uv sync
50+
uv sync $CI_UV_UPGRADE_ARG
6151
env:
52+
CI_UV_UPGRADE_ARG: ${{ matrix.dependencies == 'max' && '--upgrade' || '' }}
6253
UV_RESOLUTION: ${{ matrix.dependencies == 'min' && 'lowest-direct' || 'highest' }}
63-
CI_PYTHON_VERSION: ${{ matrix.python_version }}
6454

6555
- name: Run prek
6656
if: ${{ matrix.pre_commit == null || matrix.pre_commit == 'true' }}

pyproject.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ dynamic = [
3838
"version",
3939
]
4040
dependencies = [
41-
"click>=8.0.0",
4241
"configupdater>=3.2",
4342
"grimp>=2.5.0",
4443
"mergedeep>=1.3.4",
@@ -65,6 +64,7 @@ dev = [
6564
"uv>=0.9.1",
6665
]
6766
test = [
67+
"click>=8.1.8",
6868
"coverage[toml]>=7.7.1",
6969
"gitpython>=3.1.44",
7070
"pytest>=8.3.5",
@@ -120,13 +120,16 @@ lint.select = [
120120
"S",
121121
"SIM",
122122
"TC",
123+
"TID",
123124
"UP",
124125
]
125126
lint.ignore = [ "PLR2004", "S101", "SIM108" ]
126127
lint.per-file-ignores."!tests/**/*.py" = [ "ARG002" ]
127128
lint.per-file-ignores."tests/**/*.py" = [ "D", "INP", "S603", "TC" ]
128129
lint.flake8-bugbear.extend-immutable-calls = [ "typer.Argument", "typer.Option" ]
129130

131+
lint.flake8-tidy-imports.banned-api."typer.testing.CliRunner".msg = "Use `usethis._test.CliRunner` instead of `typer.CliRunner`."
132+
130133
lint.flake8-type-checking.quote-annotations = true
131134
lint.flake8-type-checking.runtime-evaluated-base-classes = [ "pydantic.BaseModel" ]
132135
lint.flake8-type-checking.strict = true
@@ -135,9 +138,6 @@ lint.pydocstyle.convention = "google"
135138
[tool.codespell]
136139
ignore-words-list = [ "edn" ]
137140

138-
[tool.deptry.per_rule_ignores]
139-
DEP002 = [ "click" ] # https://github.com/usethis-python/usethis-python/issues/623
140-
141141
[tool.pyproject-fmt]
142142
keep_full_version = true
143143

@@ -257,6 +257,7 @@ name = "usethis._integrations"
257257
type = "layers"
258258
layers = [
259259
"ci | pre_commit",
260+
"environ",
260261
"backend | mkdocs | pytest | pydantic | sonarqube",
261262
"project | python",
262263
"file",

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ click==8.1.8 \
166166
# import-linter
167167
# mkdocs
168168
# typer
169-
# usethis
170169
colorama==0.4.6 \
171170
--hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
172171
--hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6

src/usethis/_core/tool.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,14 @@ def use_requirements_txt(*, remove: bool = False, how: bool = False) -> None:
379379
change_toml=False,
380380
)
381381
elif backend is BackendEnum.none:
382-
# Simply dump the dependencies list to requirements.txt as-
383-
info_print(
384-
"Generating 'requirements.txt' with un-pinned, abstract dependencies."
385-
)
386-
info_print(
387-
"Consider installing 'uv' for pinned, cross-platform, full requirements files."
388-
)
382+
# Simply dump the dependencies list to requirements.txt
383+
if usethis_config.backend is BackendEnum.auto:
384+
info_print(
385+
"Generating 'requirements.txt' with un-pinned, abstract dependencies."
386+
)
387+
info_print(
388+
"Consider installing 'uv' for pinned, cross-platform, full requirements files."
389+
)
389390
tick_print("Writing 'requirements.txt'.")
390391
with open(path, "w", encoding="utf-8") as f:
391392
f.write("-e .\n")
Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
from __future__ import annotations
22

3-
from typing import Literal, TypeAlias
3+
from typing import Any, Literal, TypeAlias
44

55
from pydantic import BaseModel
6+
from ruamel.yaml.scalarstring import LiteralScalarString
67

7-
# N.B. at the point where we support more than one script item, we should create a
8-
# canonical sort order for them and enforce it when we add them to the pipeline.
9-
ScriptItemName: TypeAlias = Literal["install-uv"]
8+
ScriptItemName: TypeAlias = Literal["install-uv", "ensure-venv"]
109

1110

1211
class ScriptItemAnchor(BaseModel):
1312
name: ScriptItemName
13+
14+
15+
def anchor_name_from_script_item(item: Any) -> str | None:
16+
"""Extract the anchor name from a script item, if it has one."""
17+
if isinstance(item, LiteralScalarString):
18+
anchor = item.yaml_anchor()
19+
if anchor is not None:
20+
return anchor.value
21+
return None

src/usethis/_integrations/ci/bitbucket/io_.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def edit_bitbucket_pipelines_yaml() -> Generator[
5353

5454
if not path.exists():
5555
tick_print(f"Writing '{name}'.")
56+
# N.B. where necessary, we can opt to use a different image from this default
57+
# on a per-step basis, so this is safe even when we want to use Python images.
5658
path.write_text("image: atlassian/default-image:3", encoding="utf-8")
5759
guess_indent = False
5860
else:

src/usethis/_integrations/ci/bitbucket/steps.py

Lines changed: 75 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@
1010
import usethis._pipeweld.func
1111
from usethis._config import usethis_config
1212
from usethis._console import box_print, tick_print
13-
from usethis._integrations.backend.uv.python import (
14-
get_supported_uv_major_python_versions,
13+
from usethis._integrations.backend.dispatch import get_backend
14+
from usethis._integrations.ci.bitbucket.anchor import (
15+
ScriptItemAnchor,
16+
anchor_name_from_script_item,
1517
)
16-
from usethis._integrations.ci.bitbucket.anchor import ScriptItemAnchor
1718
from usethis._integrations.ci.bitbucket.cache import _add_caches_via_doc, remove_cache
1819
from usethis._integrations.ci.bitbucket.dump import bitbucket_fancy_dump
1920
from usethis._integrations.ci.bitbucket.errors import UnexpectedImportPipelineError
@@ -37,7 +38,9 @@
3738
StepItem,
3839
)
3940
from usethis._integrations.ci.bitbucket.schema_utils import step1tostep
41+
from usethis._integrations.environ.python import get_supported_major_python_versions
4042
from usethis._integrations.file.yaml.update import update_ruamel_yaml_map
43+
from usethis._types.backend import BackendEnum
4144

4245
if TYPE_CHECKING:
4346
from ruamel.yaml.anchor import Anchor
@@ -60,7 +63,11 @@
6063
source $HOME/.local/bin/env
6164
export UV_LINK_MODE=copy
6265
uv --version
63-
""")
66+
"""),
67+
"ensure-venv": LiteralScalarString("""\
68+
python -m venv .venv
69+
source .venv/bin/activate
70+
"""),
6471
}
6572
for name, script_item in _SCRIPT_ITEM_LOOKUP.items():
6673
script_item.yaml_set_anchor(value=name, always_dump=True)
@@ -125,6 +132,7 @@ def _add_step_in_default_via_doc(
125132

126133
# If our anchor doesn't have a definition yet, we need to add it.
127134
if script_item.name not in defined_script_item_by_name:
135+
script_item_name = script_item.name
128136
try:
129137
script_item = _SCRIPT_ITEM_LOOKUP[script_item.name]
130138
except KeyError:
@@ -134,17 +142,19 @@ def _add_step_in_default_via_doc(
134142
if config.definitions is None:
135143
config.definitions = Definitions()
136144

137-
script_items = config.definitions.script_items
145+
existing_script_items = config.definitions.script_items
138146

139-
if script_items is None:
140-
script_items = CommentedSeq()
141-
config.definitions.script_items = script_items
147+
if existing_script_items is None:
148+
existing_script_items = CommentedSeq()
149+
config.definitions.script_items = existing_script_items
142150

143-
# N.B. Once we support multiple different types of script items, we will
144-
# probably want to enforce a canonical order rather than just append.
145-
# See also anchor.py.
146-
script_items.append(script_item)
147-
script_items = CommentedSeq(script_items)
151+
# Insert script item in canonical order based on _SCRIPT_ITEM_LOOKUP
152+
insertion_index = _get_script_item_insertion_index(
153+
script_item_name=script_item_name,
154+
doc=doc,
155+
)
156+
existing_script_items.insert(insertion_index, script_item)
157+
existing_script_items = CommentedSeq(existing_script_items)
148158
else:
149159
# Otherwise, if the anchor is already defined, we need to use the
150160
# reference
@@ -158,7 +168,7 @@ def _add_step_in_default_via_doc(
158168
# N.B. Currently, we are not accounting for parallelism, whereas all these steps
159169
# could be parallel potentially.
160170
# See https://github.com/usethis-python/usethis-python/issues/149
161-
maj_versions = get_supported_uv_major_python_versions()
171+
maj_versions = get_supported_major_python_versions()
162172
step_order = [
163173
"Run pre-commit",
164174
# For these tools, sync them with the pre-commit removal logic
@@ -186,6 +196,33 @@ def _add_step_in_default_via_doc(
186196
)
187197

188198

199+
def _get_script_item_insertion_index(
200+
*, script_item_name: ScriptItemName, doc: BitbucketPipelinesYAMLDocument
201+
) -> int:
202+
"""Get the correct insertion index for a script item to maintain canonical order."""
203+
# Check if we have existing script items in the raw YAML content
204+
if not (
205+
dict(doc.content).get("definitions")
206+
and dict(doc.content["definitions"]).get("script_items")
207+
):
208+
return 0
209+
210+
existing_script_items = doc.content["definitions"]["script_items"]
211+
canonical_order = list(_SCRIPT_ITEM_LOOKUP.keys())
212+
213+
for i, existing_item in enumerate(existing_script_items):
214+
existing_name = anchor_name_from_script_item(existing_item)
215+
if (
216+
existing_name is not None
217+
and existing_name in canonical_order
218+
and canonical_order.index(script_item_name)
219+
< canonical_order.index(existing_name)
220+
):
221+
return i
222+
223+
return len(existing_script_items) # Default to end
224+
225+
189226
def remove_bitbucket_step_from_default(step: Step) -> None:
190227
"""Remove a step from the default pipeline in the Bitbucket Pipelines configuration.
191228
@@ -437,16 +474,30 @@ def add_placeholder_step_in_default(report_placeholder: bool = True) -> None:
437474

438475

439476
def _get_placeholder_step() -> Step:
440-
return Step(
441-
name=_PLACEHOLDER_NAME,
442-
script=Script(
443-
[
444-
ScriptItemAnchor(name="install-uv"),
445-
"echo 'Hello, world!'",
446-
]
447-
),
448-
caches=["uv"],
449-
)
477+
backend = get_backend()
478+
479+
if backend is BackendEnum.uv:
480+
return Step(
481+
name=_PLACEHOLDER_NAME,
482+
script=Script(
483+
[
484+
ScriptItemAnchor(name="install-uv"),
485+
"echo 'Hello, world!'",
486+
]
487+
),
488+
caches=["uv"],
489+
)
490+
elif backend is BackendEnum.none:
491+
return Step(
492+
name=_PLACEHOLDER_NAME,
493+
script=Script(
494+
[
495+
"echo 'Hello, world!'",
496+
]
497+
),
498+
)
499+
else:
500+
assert_never(backend)
450501

451502

452503
def get_defined_script_items_via_doc(

src/usethis/_integrations/environ/__init__.py

Whitespace-only changes.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from __future__ import annotations
2+
3+
from typing_extensions import assert_never
4+
5+
from usethis._integrations.backend.dispatch import get_backend
6+
from usethis._integrations.backend.uv.python import (
7+
get_supported_uv_major_python_versions,
8+
)
9+
from usethis._integrations.python.version import get_python_major_version
10+
from usethis._types.backend import BackendEnum
11+
12+
13+
def get_supported_major_python_versions() -> list[int]:
14+
backend = get_backend()
15+
16+
if backend is BackendEnum.uv:
17+
versions = get_supported_uv_major_python_versions()
18+
elif backend is BackendEnum.none:
19+
versions = [get_python_major_version()]
20+
else:
21+
assert_never(backend)
22+
23+
return versions

src/usethis/_integrations/python/version.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ def get_python_version() -> str:
1010
return _get_python_version()
1111

1212

13+
def get_python_major_version() -> int:
14+
"""Get the major version of Python."""
15+
return extract_major_version(get_python_version())
16+
17+
1318
def extract_major_version(version: str) -> int:
1419
"""Extract the major version from a version string."""
1520
return int(version.split(".")[1])

0 commit comments

Comments
 (0)