Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ exhaustive = true
name = "File Integrations Modular Design"
type = "layers"
layers = [
"pyproject_toml",
"pyproject_toml | setup_cfg",
"ini | toml | yaml",
]
containers = [ "usethis._integrations.file" ]
Expand Down
12 changes: 12 additions & 0 deletions src/usethis/_config_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
from pathlib import Path
from typing import TYPE_CHECKING

from usethis._integrations.file.ini.io_ import INIFileManager
from usethis._integrations.file.pyproject_toml.io_ import PyprojectTOMLManager
from usethis._integrations.file.setup_cfg.io_ import SetupCFGManager
from usethis._integrations.file.toml.io_ import TOMLFileManager

if TYPE_CHECKING:
Expand All @@ -15,8 +17,10 @@
def files_manager() -> Iterator[None]:
with (
PyprojectTOMLManager(),
SetupCFGManager(),
DotRuffTOMLManager(),
RuffTOMLManager(),
CodespellRCManager(),
):
yield

Expand All @@ -35,3 +39,11 @@ class RuffTOMLManager(TOMLFileManager):
@property
def relative_path(self) -> Path:
return Path("ruff.toml")


class CodespellRCManager(INIFileManager):
"""Class to manage the .codespellrc file."""

@property
def relative_path(self) -> Path:
return Path(".codespellrc")
66 changes: 29 additions & 37 deletions src/usethis/_integrations/ci/bitbucket/schema.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# generated by datamodel-codegen:
# filename: schema.json
# timestamp: 2025-01-13T20:41:41+00:00
# timestamp: 2025-03-26T14:07:35+00:00
# using the command:
# datamodel-codegen --input tests\usethis\_integrations\bitbucket\schema.json --input-file-type jsonschema --output src\usethis\_integrations\bitbucket\schema.py --enum-field-as-literal all --field-constraints --use-double-quotes --use-union-operator --use-standard-collections --use-default-kwarg --output-model-type pydantic_v2.BaseModel --target-python-version 3.10
# datamodel-codegen --input tests\usethis\_integrations\ci\bitbucket\schema.json --input-file-type jsonschema --output src\usethis\_integrations\ci\bitbucket\schema.py --enum-field-as-literal all --field-constraints --use-double-quotes --use-union-operator --use-standard-collections --use-default-kwarg --output-model-type pydantic_v2.BaseModel --target-python-version 3.10
# ruff: noqa: ERA001
# pyright: reportAssignmentType=false
# plus manually add Definitions.scripts for type hinting
# plus manually add Definitions.script_items for type hinting
# plus manually add ScriptItemAnchor as a root type of Script, and import it
# plus manually forbid StepItem.step from being None
# plus manually forbid Step1.step from being None
Expand All @@ -31,19 +31,6 @@ class Depth(RootModel[int]):
)


class SparseCheckout(BaseModel):
cone_mode: bool | None = Field(
default=True,
alias="cone-mode",
description="Controls whether to use cone-mode or non-cone-mode.",
)
enabled: bool | None = Field(default=True, description="Enables sparse checkout.")
patterns: list[str] | None = Field(
default=None,
description="List of patterns to include in sparse checkout. The patterns should be directories or gitignore-style patterns based on the cone-mode settings.",
)


class Clone(BaseModel):
depth: Depth | Literal["full"] | None = Field(
default=50,
Expand All @@ -54,10 +41,6 @@ class Clone(BaseModel):
enabled: bool | None = Field(
default=True, description="Enables cloning of the repository."
)
filter: str | None = Field(
default=None,
description='The partial clone filter argument of Git fetch operation. It can be either "blob:none" or "tree:<n>" value',
)
lfs: bool | None = Field(
default=False,
description="Enables the download of files from LFS storage when cloning.",
Expand All @@ -67,19 +50,6 @@ class Clone(BaseModel):
alias="skip-ssl-verify",
description="Disables SSL verification during Git clone operation, allowing the use of self-signed certificates.",
)
sparse_checkout: SparseCheckout | None = Field(
default=None,
alias="sparse-checkout",
description="When this is provided, the repository will be cloned using sparse checkout using the provided settings.",
title="Sparse Checkout Settings",
)
strategy: Literal["clone", "fetch"] | None = Field(
default="fetch",
description='Set the Git clone strategy to use. "fetch" is the new default strategy, "clone" is the legacy strategy.',
)
tags: bool | None = Field(
default=False, description="Enables fetching tags when cloning."
)


class MaxTime(RootModel[int]):
Expand Down Expand Up @@ -241,6 +211,14 @@ class FailFast(RootModel[bool]):
root: bool = Field(..., title="Fail Fast")


class Download(RootModel[list[str]]):
root: list[str] = Field(
...,
description="Define the list of filtered artifacts to be downloaded by the step.",
min_length=1,
)


class ArtifactsPaths(RootModel[list[str]]):
root: list[str] = Field(..., min_length=1)

Expand All @@ -262,6 +240,22 @@ class RunsOnItem(RootModel[str]):
)


class ArtifactsUpload(BaseModel):
depth: int | None = Field(
default=None,
description="The depth to search for the artifact files.",
ge=1,
title="Artifact Depth",
)
name: str = Field(
..., description="The name of the artifact.", title="Artifact Name"
)
paths: ArtifactsPaths
type: Literal["shared"] | None = Field(
default="shared", description="The type of the artifact.", title="Artifact Type"
)


class Runtime(BaseModel):
cloud: Cloud | None = None

Expand Down Expand Up @@ -299,11 +293,9 @@ class Script(RootModel[list[str | Pipe | ScriptItemAnchor]]):


class ArtifactsExpanded(BaseModel):
download: bool | None = Field(
default=True,
description="Enables downloading of all available artifacts at the start of a step.",
)
download: bool | Download | None = None
paths: ArtifactsPaths | None = None
upload: list[ArtifactsUpload] | None = Field(default=None, min_length=1)


class RunsOnExpanded(RootModel[list[RunsOnItem]]):
Expand Down
Empty file.
38 changes: 38 additions & 0 deletions src/usethis/_integrations/file/setup_cfg/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from __future__ import annotations

from usethis._integrations.file.ini.errors import (
INIError,
ININotFoundError,
INIValueAlreadySetError,
INIValueMissingError,
UnexpectedINIIOError,
UnexpectedINIOpenError,
)


class SetupCFGError(INIError):
"""Raised when aspects of 'setup.cfg' are missing, invalid, or unexpected."""


class SetupCFGNotFoundError(SetupCFGError, ININotFoundError):
"""Raised when a setup.cfg file is not found."""


class SetupCFGDecodeError(SetupCFGError):
"""Raised when a setup.cfg file cannot be decoded."""


class UnexpectedSetupCFGOpenError(SetupCFGError, UnexpectedINIOpenError):
"""Raised when the setup.cfg file is unexpectedly opened."""


class UnexpectedSetupCFGIOError(SetupCFGError, UnexpectedINIIOError):
"""Raised when an unexpected attempt is made to read or write the setup.cfg file."""


class SetupCFGValueAlreadySetError(SetupCFGError, INIValueAlreadySetError):
"""Raised when a value is unexpectedly already set in the 'setup.cfg' file."""


class SetupCFGValueMissingError(SetupCFGError, INIValueMissingError):
"""Raised when a value is unexpectedly missing from the 'setup.cfg' file."""
73 changes: 73 additions & 0 deletions src/usethis/_integrations/file/setup_cfg/io_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from usethis._integrations.file.ini.errors import (
INIDecodeError,
ININotFoundError,
INIValueAlreadySetError,
INIValueMissingError,
UnexpectedINIIOError,
UnexpectedINIOpenError,
)
from usethis._integrations.file.ini.io_ import INIFileManager
from usethis._integrations.file.setup_cfg.errors import (
SetupCFGDecodeError,
SetupCFGNotFoundError,
SetupCFGValueAlreadySetError,
SetupCFGValueMissingError,
UnexpectedSetupCFGIOError,
UnexpectedSetupCFGOpenError,
)

if TYPE_CHECKING:
from typing import Any

from typing_extensions import Self


class SetupCFGManager(INIFileManager):
"""Manages the setup.cfg file."""

@property
def relative_path(self) -> Path:
return Path("setup.cfg")

def __enter__(self) -> Self:
try:
return super().__enter__()
except UnexpectedINIOpenError as err:
raise UnexpectedSetupCFGOpenError(err) from None

def read_file(self) -> None:
try:
super().read_file()
except ININotFoundError as err:
raise SetupCFGNotFoundError(err) from None
except UnexpectedINIIOError as err:
raise UnexpectedSetupCFGIOError(err) from None
except INIDecodeError as err:
raise SetupCFGDecodeError(err) from None

def _validate_lock(self) -> None:
try:
super()._validate_lock()
except UnexpectedINIIOError as err:
raise UnexpectedSetupCFGIOError(err) from None

def set_value(
self, *, keys: list[str], value: Any, exists_ok: bool = False
) -> None:
"""Set a value in the pyproject.toml configuration file."""
try:
super().set_value(keys=keys, value=value, exists_ok=exists_ok)
except INIValueAlreadySetError as err:
raise SetupCFGValueAlreadySetError(err) from None

def __delitem__(self, keys: list[str]) -> None:
"""Remove a value from the pyproject.toml configuration file."""
try:
super().__delitem__(keys)
except INIValueMissingError as err:
raise SetupCFGValueMissingError(err) from None
Loading