Migrate to uv

This commit is contained in:
Kivi Kaitaniemi
2025-12-12 17:03:15 +02:00
parent a566adf546
commit 9da024c8bd
13 changed files with 272 additions and 216 deletions
+1
View File
@@ -4,4 +4,5 @@ build/
*.egg-info/ *.egg-info/
venv/ venv/
.venv/
dist/ dist/
+41
View File
@@ -0,0 +1,41 @@
# Commands used in development
Before committing ensure all tests pass and format files.
## Running
Run decman as root to test all changes:
```sh
sudo uv run decman
```
## Testing
Run unit tests:
```sh
uv run python -m unittest
```
## Formatting
Format all files:
```sh
uv run ruff format
```
## Linting
Run lints:
```sh
uv run ruff check
```
Apply fixes:
```sh
uv run ruff check --fix
```
+1 -3
View File
@@ -72,9 +72,7 @@ class MyModule(Module):
def directories(self) -> dict[str, Directory]: def directories(self) -> dict[str, Directory]:
# Directories are handeled the same way. Variables are substituted in text files. # Directories are handeled the same way. Variables are substituted in text files.
return { return {
"/home/kk/.config/mod-app/": Directory( "/home/kk/.config/mod-app/": Directory(source_directory="files/app-config", owner="kk")
source_directory="files/app-config", owner="kk"
)
} }
# Packages and systemd units are basically the same with modules as without modules. # Packages and systemd units are basically the same with modules as without modules.
+1 -3
View File
@@ -115,9 +115,7 @@ decman.enabled_systemd_units += ["NetworkManager.service"]
# You can manage units for users as well. # You can manage units for users as well.
# Ensure that previous user unit declarations aren't overwritten and they are initialized. # Ensure that previous user unit declarations aren't overwritten and they are initialized.
decman.enabled_systemd_user_units["kk"] = decman.enabled_systemd_user_units.get( decman.enabled_systemd_user_units["kk"] = decman.enabled_systemd_user_units.get("kk", [])
"kk", []
)
# Add user unit. # Add user unit.
decman.enabled_systemd_user_units["kk"].append("syncthing.service") decman.enabled_systemd_user_units["kk"].append("syncthing.service")
+23 -6
View File
@@ -1,7 +1,3 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project] [project]
name = "decman" name = "decman"
version = "0.4.1" version = "0.4.1"
@@ -10,6 +6,7 @@ license = {file = "LICENSE"}
authors = [ authors = [
{name = "Kivi Kaitaniemi"} {name = "Kivi Kaitaniemi"}
] ]
requires-python = ">=3.13"
dependencies = [ dependencies = [
"requests", "requests",
] ]
@@ -19,6 +16,26 @@ decman = "decman.app:main"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"isort>=5.13.2", "ruff>=0.14.9",
"ruff>=0.9.3",
] ]
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[tool.ruff]
line-length = 100
target-version = "py313"
[tool.ruff.lint]
select = [
"E", "F", "W", # base style/errors
"I", # import sorting
"B", # bugbear
]
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
skip-magic-trailing-comma = false
+3 -9
View File
@@ -52,9 +52,7 @@ def sh(
f"Running user defined shell command failed because the user {user} doesn't exist." f"Running user defined shell command failed because the user {user} doesn't exist."
) from e ) from e
with subprocess.Popen( with subprocess.Popen(sh_cmd, shell=True, group=gid, user=uid, env=env) as process:
sh_cmd, shell=True, group=gid, user=uid, env=env
) as process:
if process.wait() != 0: if process.wait() != 0:
raise decman.error.UserFacingError( raise decman.error.UserFacingError(
f"Running user shell command '{sh_cmd}' as {user} failed." f"Running user shell command '{sh_cmd}' as {user} failed."
@@ -144,9 +142,7 @@ class File:
target_directory = os.path.dirname(target) target_directory = os.path.dirname(target)
def create_missing_dirs( def create_missing_dirs(dirct: str, uid: typing.Optional[int], gid: typing.Optional[int]):
dirct: str, uid: typing.Optional[int], gid: typing.Optional[int]
):
if not os.path.isdir(dirct): if not os.path.isdir(dirct):
parent_dir = os.path.dirname(dirct) parent_dir = os.path.dirname(dirct)
if not os.path.isdir(parent_dir): if not os.path.isdir(parent_dir):
@@ -183,9 +179,7 @@ class File:
with open(target, "wt", encoding=self.encoding) as file: with open(target, "wt", encoding=self.encoding) as file:
file.write(content) file.write(content)
else: else:
assert self.content is not None, ( assert self.content is not None, "Content should be set since source_file was not set."
"Content should be set since source_file was not set."
)
content = self.content content = self.content
for var, value in variables.items(): for var, value in variables.items():
content = content.replace(var, value) content = content.replace(var, value)
+11 -33
View File
@@ -30,9 +30,7 @@ def main():
epilog="See more help at: https://github.com/kiviktnm/decman", epilog="See more help at: https://github.com/kiviktnm/decman",
) )
parser.add_argument( parser.add_argument("--source", action="store", help="python file containing configuration")
"--source", action="store", help="python file containing configuration"
)
parser.add_argument( parser.add_argument(
"--print", "--print",
"--dry-run", "--dry-run",
@@ -40,9 +38,7 @@ def main():
default=False, default=False,
help="print what would happen as a result of running decman", help="print what would happen as a result of running decman",
) )
parser.add_argument( parser.add_argument("--debug", action="store_true", default=False, help="show debug output")
"--debug", action="store_true", default=False, help="show debug output"
)
parser.add_argument( parser.add_argument(
"--no-packages", "--no-packages",
action="store_true", action="store_true",
@@ -174,9 +170,7 @@ def _set_up(store: l.Store, args):
with open(source_path, "rt", encoding="utf-8") as file: with open(source_path, "rt", encoding="utf-8") as file:
content = file.read() content = file.read()
except OSError as e: except OSError as e:
raise err.UserFacingError( raise err.UserFacingError(f"Failed to read source file '{store.source_file}'.") from e
f"Failed to read source file '{store.source_file}'."
) from e
os.chdir(source_dir) os.chdir(source_dir)
sys.path.append(".") sys.path.append(".")
@@ -227,9 +221,7 @@ class Core:
self.fpkg_search = fpm.ExtendedPackageSearch(self.pacman) self.fpkg_search = fpm.ExtendedPackageSearch(self.pacman)
for upkg in self.source.all_user_pkgs(): for upkg in self.source.all_user_pkgs():
self.fpkg_search.add_user_pkg( self.fpkg_search.add_user_pkg(fpm.PackageInfo.from_user_package(upkg, self.pacman))
fpm.PackageInfo.from_user_package(upkg, self.pacman)
)
self.fpm = fpm.ForeignPackageManager(store, self.pacman, self.fpkg_search) self.fpm = fpm.ForeignPackageManager(store, self.pacman, self.fpkg_search)
@@ -284,9 +276,7 @@ class Core:
to_remove = self.source.packages_to_remove(currently_installed) to_remove = self.source.packages_to_remove(currently_installed)
currently_installed_flatpak = self.flatpak.get_installed() currently_installed_flatpak = self.flatpak.get_installed()
to_remove_flatpak = self.source.flatpak_packages_to_remove( to_remove_flatpak = self.source.flatpak_packages_to_remove(currently_installed_flatpak)
currently_installed_flatpak
)
l.print_list("Removing pacman packages:", to_remove) l.print_list("Removing pacman packages:", to_remove)
@@ -307,16 +297,12 @@ class Core:
def _remove_user_flatpaks(self, only_print: bool = False): def _remove_user_flatpaks(self, only_print: bool = False):
# Get all non system users (users that have uid >= 1000), also ignore nobody # Get all non system users (users that have uid >= 1000), also ignore nobody
users = [ users = [
u.pw_name u.pw_name for u in pwd.getpwall() if u.pw_uid >= 1000 and u.pw_name not in ("nobody",)
for u in pwd.getpwall()
if u.pw_uid >= 1000 and u.pw_name not in ("nobody",)
] ]
# Add root to users # Add root to users
users.append("root") users.append("root")
for user in users: for user in users:
currently_installed_flatpak = self.flatpak.get_installed( currently_installed_flatpak = self.flatpak.get_installed(as_user=True, which_user=user)
as_user=True, which_user=user
)
to_remove_flatpak = self.source.flatpak_packages_to_remove( to_remove_flatpak = self.source.flatpak_packages_to_remove(
currently_installed_flatpak, as_user=True, which_user=user currently_installed_flatpak, as_user=True, which_user=user
) )
@@ -341,9 +327,7 @@ class Core:
self.pacman.upgrade() self.pacman.upgrade()
if conf.enable_fpm and self.update_foreign_packages: if conf.enable_fpm and self.update_foreign_packages:
self.fpm.upgrade( self.fpm.upgrade(self.upgrade_devel, self.force_build, self.source.ignored_packages)
self.upgrade_devel, self.force_build, self.source.ignored_packages
)
# flatpak # flatpak
if conf.enable_flatpak and self.update_flatpaks: if conf.enable_flatpak and self.update_flatpaks:
@@ -372,9 +356,7 @@ class Core:
# flatpak # flatpak
currently_installed_flatpak = self.flatpak.get_installed() currently_installed_flatpak = self.flatpak.get_installed()
to_install_flatpak = self.source.flatpak_packages_to_install( to_install_flatpak = self.source.flatpak_packages_to_install(currently_installed_flatpak)
currently_installed_flatpak
)
l.print_list("Installing pacman packages:", to_install_pacman) l.print_list("Installing pacman packages:", to_install_pacman)
@@ -402,16 +384,12 @@ class Core:
def _install_user_flatpaks(self, only_print: bool = False): def _install_user_flatpaks(self, only_print: bool = False):
# Get all non system users (users that have uid >= 1000), also ignore nobody # Get all non system users (users that have uid >= 1000), also ignore nobody
users = [ users = [
u.pw_name u.pw_name for u in pwd.getpwall() if u.pw_uid >= 1000 and u.pw_name not in ("nobody",)
for u in pwd.getpwall()
if u.pw_uid >= 1000 and u.pw_name not in ("nobody",)
] ]
# Add root to users # Add root to users
users.append("root") users.append("root")
for user in users: for user in users:
currently_installed_flatpak = self.flatpak.get_installed( currently_installed_flatpak = self.flatpak.get_installed(as_user=True, which_user=user)
as_user=True, which_user=user
)
to_install_flatpak = self.source.flatpak_packages_to_install( to_install_flatpak = self.source.flatpak_packages_to_install(
currently_installed_flatpak, as_user=True, which_user=user currently_installed_flatpak, as_user=True, which_user=user
) )
+17 -51
View File
@@ -93,9 +93,7 @@ def print_list(
if limit_to_term_size: if limit_to_term_size:
max_line_width = ( max_line_width = (
shutil.get_terminal_size().columns shutil.get_terminal_size().columns - len(_SPACING) - len(_CONTINUATION_PREFIX)
- len(_SPACING)
- len(_CONTINUATION_PREFIX)
) )
lines = [f"{l.pop(0)}"] lines = [f"{l.pop(0)}"]
@@ -144,9 +142,7 @@ def prompt_number(
Prompts the user for a integer. Prompts the user for a integer.
""" """
while True: while True:
i = input( i = input(f"{_DECMAN_MSG_TAG} {_GREEN_PREFIX}PROMPT{_RESET_SUFFIX}: {msg}").strip()
f"{_DECMAN_MSG_TAG} {_GREEN_PREFIX}PROMPT{_RESET_SUFFIX}: {msg}"
).strip()
if default is not None and i == "": if default is not None and i == "":
return default return default
@@ -268,9 +264,7 @@ class Store:
if latest_path is None: if latest_path is None:
return None return None
assert latest_version is not None, ( assert latest_version is not None, "If latest_path is set, then latest_version is set."
"If latest_path is set, then latest_version is set."
)
return (latest_version, latest_path) return (latest_version, latest_path)
def add_package_to_cache(self, package: str, version: str, path_to_built_pkg: str): def add_package_to_cache(self, package: str, version: str, path_to_built_pkg: str):
@@ -320,9 +314,7 @@ class Store:
os.remove(oldest_path) os.remove(oldest_path)
except OSError as e: except OSError as e:
print_error(f"{e}") print_error(f"{e}")
print_error( print_error(f"Failed to remove file '{oldest_path}' from the package cache.")
f"Failed to remove file '{oldest_path}' from the package cache."
)
print_continuation("You'll have to remove the file manually.") print_continuation("You'll have to remove the file manually.")
self._package_file_cache[package] = entries self._package_file_cache[package] = entries
@@ -493,9 +485,7 @@ class Source:
file.copy_to(target, variables) file.copy_to(target, variables)
except OSError as e: except OSError as e:
print_error(f"{e}") print_error(f"{e}")
raise err.UserFacingError( raise err.UserFacingError(f"Failed to install file to {target}.") from e
f"Failed to install file to {target}."
) from e
def install_dirs( def install_dirs(
dirs: dict[str, decman.Directory], dirs: dict[str, decman.Directory],
@@ -504,14 +494,10 @@ class Source:
for target, directory in dirs.items(): for target, directory in dirs.items():
try: try:
print_debug(f"Installing directory to {target}.") print_debug(f"Installing directory to {target}.")
created_files.extend( created_files.extend(directory.copy_to(target, variables, only_print))
directory.copy_to(target, variables, only_print)
)
except OSError as e: except OSError as e:
print_error(f"{e}") print_error(f"{e}")
raise err.UserFacingError( raise err.UserFacingError(f"Failed to install directory to {target}.") from e
f"Failed to install directory to {target}."
) from e
install_files(self.files) install_files(self.files)
install_dirs(self.directories) install_dirs(self.directories)
@@ -616,9 +602,7 @@ class Source:
result.append(pkg) result.append(pkg)
return result return result
def pacman_packages_to_install( def pacman_packages_to_install(self, currently_installed_packages: list[str]) -> list[str]:
self, currently_installed_packages: list[str]
) -> list[str]:
""" """
Returns all pacman packages that should be installed. Returns all pacman packages that should be installed.
""" """
@@ -630,9 +614,7 @@ class Source:
result.append(pkg) result.append(pkg)
return result return result
def foreign_packages_to_install( def foreign_packages_to_install(self, currently_installed_packages: list[str]) -> list[str]:
self, currently_installed_packages: list[str]
) -> list[str]:
""" """
Returns all aur and user packages that should be installed. Returns all aur and user packages that should be installed.
""" """
@@ -710,16 +692,12 @@ class Source:
result.update(module.pacman_packages()) result.update(module.pacman_packages())
return result return result
def _all_flatpak_packages( def _all_flatpak_packages(self, as_user: bool = False, which_user: str = "") -> set[str]:
self, as_user: bool = False, which_user: str = ""
) -> set[str]:
# loop through all the user packages and save which ones are owned by the currently selected user # loop through all the user packages and save which ones are owned by the currently selected user
current_user_flatpak_packages = self.flatpak_user_packages.get(which_user, []) current_user_flatpak_packages = self.flatpak_user_packages.get(which_user, [])
result = set() result = set()
result.update( result.update(self.flatpak_packages if not as_user else current_user_flatpak_packages)
self.flatpak_packages if not as_user else current_user_flatpak_packages
)
for module in self.modules: for module in self.modules:
if not module.enabled: if not module.enabled:
continue continue
@@ -847,9 +825,7 @@ class Pacman:
if not packages: if not packages:
return return
returncode, output = echo_and_capture_command( returncode, output = echo_and_capture_command(conf.commands.install_pkgs(packages))
conf.commands.install_pkgs(packages)
)
if returncode != 0: if returncode != 0:
raise err.UserFacingError( raise err.UserFacingError(
f"Failed to install packages using pacman. Process exited with code {returncode}." f"Failed to install packages using pacman. Process exited with code {returncode}."
@@ -891,9 +867,7 @@ class Pacman:
if not files: if not files:
return return
returncode, output = echo_and_capture_command( returncode, output = echo_and_capture_command(conf.commands.install_files(files))
conf.commands.install_files(files)
)
if returncode != 0: if returncode != 0:
raise err.UserFacingError( raise err.UserFacingError(
f"Failed to install package files using pacman. Process exited with code {returncode}." f"Failed to install package files using pacman. Process exited with code {returncode}."
@@ -1035,9 +1009,7 @@ class Flatpak:
user_facing_msg=f"Failed to get installed flatpak packages using '{error.cmd}'. Output: {error.stdout}." user_facing_msg=f"Failed to get installed flatpak packages using '{error.cmd}'. Output: {error.stdout}."
) from error ) from error
def install( def install(self, packages: list[str], as_user: bool = False, which_user: str = "root"):
self, packages: list[str], as_user: bool = False, which_user: str = "root"
):
""" """
Install the listed flatpak packages. Install the listed flatpak packages.
""" """
@@ -1089,9 +1061,7 @@ class Flatpak:
f"Failed to upgrade flatpak packages. Process exited with code {proc.returncode}." f"Failed to upgrade flatpak packages. Process exited with code {proc.returncode}."
) )
def remove( def remove(self, packages: list[str], as_user: bool = False, which_user: str = "root"):
self, packages: list[str], as_user: bool = False, which_user: str = "root"
):
""" """
Remove all the listed packages and their unused dependecies. This has to happen in two steps. Remove all the listed packages and their unused dependecies. This has to happen in two steps.
""" """
@@ -1155,9 +1125,7 @@ class Systemd:
capture_output=conf.suppress_command_output, capture_output=conf.suppress_command_output,
) )
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:
raise err.UserFacingError( raise err.UserFacingError(f"Failed to enable systemd units: {units}") from error
f"Failed to enable systemd units: {units}"
) from error
self.state.enabled_systemd_units += units self.state.enabled_systemd_units += units
def disable_units(self, units: list[str]): def disable_units(self, units: list[str]):
@@ -1174,9 +1142,7 @@ class Systemd:
capture_output=conf.suppress_command_output, capture_output=conf.suppress_command_output,
) )
except subprocess.CalledProcessError as error: except subprocess.CalledProcessError as error:
raise err.UserFacingError( raise err.UserFacingError(f"Failed to disable systemd units: {units}") from error
f"Failed to disable systemd units: {units}"
) from error
for unit in units: for unit in units:
try: try:
self.state.enabled_systemd_units.remove(unit) self.state.enabled_systemd_units.remove(unit)
+27 -79
View File
@@ -93,17 +93,13 @@ class PackageInfo:
if pacman.is_installable(make_dep): if pacman.is_installable(make_dep):
self.pacman_make_dependencies.append(make_dep) self.pacman_make_dependencies.append(make_dep)
else: else:
self.foreign_make_dependencies_stripped.append( self.foreign_make_dependencies_stripped.append(strip_dependency(make_dep))
strip_dependency(make_dep)
)
for check_dep in check_dependencies: for check_dep in check_dependencies:
if pacman.is_installable(check_dep): if pacman.is_installable(check_dep):
self.pacman_check_dependencies.append(check_dep) self.pacman_check_dependencies.append(check_dep)
else: else:
self.foreign_check_dependencies_stripped.append( self.foreign_check_dependencies_stripped.append(strip_dependency(check_dep))
strip_dependency(check_dep)
)
def pkg_file_prefix(self) -> str: def pkg_file_prefix(self) -> str:
""" """
@@ -112,9 +108,7 @@ class PackageInfo:
return f"{self.pkgname}-{self.version}" return f"{self.pkgname}-{self.version}"
@staticmethod @staticmethod
def from_user_package( def from_user_package(user_package: decman.UserPackage, pacman: l.Pacman) -> "PackageInfo":
user_package: decman.UserPackage, pacman: l.Pacman
) -> "PackageInfo":
""" """
Converts a UserPackage to PackageInfo Converts a UserPackage to PackageInfo
""" """
@@ -144,8 +138,7 @@ class ForeignPackage:
if isinstance(value, self.__class__): if isinstance(value, self.__class__):
return ( return (
self.name == value.name self.name == value.name
and self._all_recursive_foreign_deps and self._all_recursive_foreign_deps == value._all_recursive_foreign_deps
== value._all_recursive_foreign_deps
) )
return False return False
@@ -206,9 +199,7 @@ class DepGraph:
The parent is the package that requires the child package. The parent is the package that requires the child package.
""" """
child_node = self.package_nodes.get( child_node = self.package_nodes.get(child_pkgname, DepNode(ForeignPackage(child_pkgname)))
child_pkgname, DepNode(ForeignPackage(child_pkgname))
)
self.package_nodes[child_pkgname] = child_node self.package_nodes[child_pkgname] = child_node
if len(child_node.children) == 0: if len(child_node.children) == 0:
@@ -443,16 +434,10 @@ class ExtendedPackageSearch:
return pkg return pkg
if len(known_pkg_results) > 1: if len(known_pkg_results) > 1:
return self._choose_provider( return self._choose_provider(stripped_dependency, known_pkg_results, "user packages")
stripped_dependency, known_pkg_results, "user packages"
)
url = ( url = f"https://aur.archlinux.org/rpc/v5/search/{stripped_dependency}?by=provides"
f"https://aur.archlinux.org/rpc/v5/search/{stripped_dependency}?by=provides" l.print_debug(f"Requesting providers for '{stripped_dependency}' from AUR. URL = {url}")
)
l.print_debug(
f"Requesting providers for '{stripped_dependency}' from AUR. URL = {url}"
)
try: try:
request = requests.get(url, timeout=conf.aur_rpc_timeout) request = requests.get(url, timeout=conf.aur_rpc_timeout)
d = request.json() d = request.json()
@@ -486,9 +471,7 @@ class ExtendedPackageSearch:
) -> typing.Optional[PackageInfo]: ) -> typing.Optional[PackageInfo]:
min_selection = 1 min_selection = 1
max_selection = len(possible_providers) max_selection = len(possible_providers)
l.print_summary( l.print_summary(f"Found {len(possible_providers)} providers for {dep} from {where}.")
f"Found {len(possible_providers)} providers for {dep} from {where}."
)
providers = "Providers: " providers = "Providers: "
for index, name in enumerate(possible_providers): for index, name in enumerate(possible_providers):
@@ -606,9 +589,7 @@ class ForeignPackageManager:
else: else:
as_deps.append(pkg) as_deps.append(pkg)
l.print_debug( l.print_debug(f"The following foreign packages will be upgraded: {' '.join(as_explicit)}")
f"The following foreign packages will be upgraded: {' '.join(as_explicit)}"
)
self.install(as_explicit, as_deps, force) self.install(as_explicit, as_deps, force)
@@ -628,9 +609,7 @@ class ForeignPackageManager:
if len(foreign_pkgs) == 0 and len(foreign_dep_pkgs) == 0: if len(foreign_pkgs) == 0 and len(foreign_dep_pkgs) == 0:
return return
resolved_dependencies = self.resolve_dependencies( resolved_dependencies = self.resolve_dependencies(foreign_pkgs, foreign_dep_pkgs)
foreign_pkgs, foreign_dep_pkgs
)
l.print_list( l.print_list(
"The following foreign packages will be installed explicitly:", "The following foreign packages will be installed explicitly:",
@@ -657,20 +636,15 @@ class ForeignPackageManager:
self._pacman.install_dependencies(list(resolved_dependencies.pacman_deps)) self._pacman.install_dependencies(list(resolved_dependencies.pacman_deps))
try: try:
with PackageBuilder( with PackageBuilder(self._search, self._store, resolved_dependencies) as builder:
self._search, self._store, resolved_dependencies
) as builder:
while resolved_dependencies.build_order: while resolved_dependencies.build_order:
to_build = resolved_dependencies.build_order.pop(0) to_build = resolved_dependencies.build_order.pop(0)
pkgbase = resolved_dependencies.get_pkgbase(to_build) pkgbase = resolved_dependencies.get_pkgbase(to_build)
package_names = resolved_dependencies.get_pkgs_with_common_pkgbase( package_names = resolved_dependencies.get_pkgs_with_common_pkgbase(to_build)
to_build
)
packages = [ packages = [
resolved_dependencies.packages[pkgname] resolved_dependencies.packages[pkgname] for pkgname in package_names
for pkgname in package_names
] ]
builder.build_packages(pkgbase, packages, force) builder.build_packages(pkgbase, packages, force)
@@ -756,13 +730,10 @@ class ForeignPackageManager:
result.add_pkgbase_info(pkgname, info.pkgbase) result.add_pkgbase_info(pkgname, info.pkgbase)
build_deps = ( build_deps = (
info.foreign_make_dependencies_stripped info.foreign_make_dependencies_stripped + info.foreign_check_dependencies_stripped
+ info.foreign_check_dependencies_stripped
) )
self._search.try_caching_packages( self._search.try_caching_packages(info.foreign_dependencies_stripped + build_deps)
info.foreign_dependencies_stripped + build_deps
)
for depname in info.foreign_dependencies_stripped: for depname in info.foreign_dependencies_stripped:
process_dep(pkgname, depname, result.foreign_dep_pkgs) process_dep(pkgname, depname, result.foreign_dep_pkgs)
@@ -819,9 +790,7 @@ class ForeignPackageManager:
return should_upgrade return should_upgrade
except (ValueError, subprocess.CalledProcessError) as error: except (ValueError, subprocess.CalledProcessError) as error:
l.print_error(f"{error}") l.print_error(f"{error}")
raise err.UserFacingError( raise err.UserFacingError("Failed to compare versions using vercmp.") from error
"Failed to compare versions using vercmp."
) from error
class PackageBuilder: class PackageBuilder:
@@ -926,9 +895,7 @@ class PackageBuilder:
""" """
shutil.rmtree(conf.build_dir) shutil.rmtree(conf.build_dir)
def build_packages( def build_packages(self, package_base: str, packages: list[ForeignPackage], force: bool):
self, package_base: str, packages: list[ForeignPackage], force: bool
):
""" """
Builds package(s) with the same package base. Builds package(s) with the same package base.
@@ -940,9 +907,7 @@ class PackageBuilder:
# Rebuild is only needed if at least one package is not in the cache. # Rebuild is only needed if at least one package is not in the cache.
if self._are_all_pkgs_cached(packages) and not force: if self._are_all_pkgs_cached(packages) and not force:
l.print_info( l.print_info(f"Skipped building '{' '.join(package_names)}'. Already up to date.")
f"Skipped building '{' '.join(package_names)}'. Already up to date."
)
return return
l.print_info(f"Building '{' '.join(package_names)}'.") l.print_info(f"Building '{' '.join(package_names)}'.")
@@ -952,9 +917,7 @@ class PackageBuilder:
pkgbuild_dir = self.pkgbase_dir_map[package_base] pkgbuild_dir = self.pkgbase_dir_map[package_base]
os.chdir(pkgbuild_dir) os.chdir(pkgbuild_dir)
l.print_debug( l.print_debug(f"Chroot dir is: '{self.chroot_dir}', pkgbuild dir is '{pkgbuild_dir}'.")
f"Chroot dir is: '{self.chroot_dir}', pkgbuild dir is '{pkgbuild_dir}'."
)
l.print_info("Installing build dependencies to chroot.") l.print_info("Installing build dependencies to chroot.")
@@ -970,9 +933,7 @@ class PackageBuilder:
l.print_info("Making package.") l.print_info("Making package.")
subprocess.run( subprocess.run(
conf.commands.make_chroot_pkg( conf.commands.make_chroot_pkg(self.chroot_wd_dir, conf.makepkg_user, chroot_pkg_files),
self.chroot_wd_dir, conf.makepkg_user, chroot_pkg_files
),
check=True, check=True,
capture_output=conf.quiet_output, capture_output=conf.quiet_output,
) )
@@ -989,9 +950,7 @@ class PackageBuilder:
assert pkg_info is not None assert pkg_info is not None
version = pkg_info.version version = pkg_info.version
l.print_debug( l.print_debug(f"Adding '{pkgname}', version: '{version}' to cache as file '{dest}'.")
f"Adding '{pkgname}', version: '{version}' to cache as file '{dest}'."
)
self._store.add_package_to_cache(pkgname, version, dest) self._store.add_package_to_cache(pkgname, version, dest)
@@ -1132,12 +1091,8 @@ before and thus are found in the cache."
capture_output=conf.suppress_command_output, capture_output=conf.suppress_command_output,
) )
if l.prompt_confirm( if l.prompt_confirm(f"Review PKGBUILD or show diff for {pkgbase}?", default=True):
f"Review PKGBUILD or show diff for {pkgbase}?", default=True latest_reviewed_commit = self._store.pkgbuild_latest_reviewed_commits.get(pkgbase)
):
latest_reviewed_commit = (
self._store.pkgbuild_latest_reviewed_commits.get(pkgbase)
)
git_commit_ids = ( git_commit_ids = (
subprocess.run( subprocess.run(
@@ -1150,19 +1105,12 @@ before and thus are found in the cache."
.split("\n") .split("\n")
) )
if ( if latest_reviewed_commit is None or latest_reviewed_commit not in git_commit_ids:
latest_reviewed_commit is None
or latest_reviewed_commit not in git_commit_ids
):
for file in os.scandir("."): for file in os.scandir("."):
if file.is_file() and not file.name.startswith("."): if file.is_file() and not file.name.startswith("."):
subprocess.run( subprocess.run(conf.commands.review_file(file.path), check=True)
conf.commands.review_file(file.path), check=True
)
else: else:
subprocess.run( subprocess.run(conf.commands.git_diff(latest_reviewed_commit), check=True)
conf.commands.git_diff(latest_reviewed_commit), check=True
)
if l.prompt_confirm("Build this package?", default=True): if l.prompt_confirm("Build this package?", default=True):
commit_id = ( commit_id = (
+8 -13
View File
@@ -1,5 +1,4 @@
import os import os
import shutil
import sys import sys
# This test is manual. You'll have to verify the results manually. # This test is manual. You'll have to verify the results manually.
@@ -12,23 +11,22 @@ os.chdir(cd)
sys.path.append(os.path.join(cd, "../../src/.")) sys.path.append(os.path.join(cd, "../../src/."))
from decman import File, Directory from decman import Directory, File
#if os.path.exists("/tmp/decman-files"): # if os.path.exists("/tmp/decman-files"):
# shutil.rmtree("/tmp/decman-files") # shutil.rmtree("/tmp/decman-files")
#os.makedirs("/tmp/decman-files") # os.makedirs("/tmp/decman-files")
f1 = File(source_file="src/f1.txt") f1 = File(source_file="src/f1.txt")
f1.copy_to( f1.copy_to(
"/tmp/decman-files/f1.txt", "/tmp/decman-files/f1.txt",
variables={ variables={"%variable%": "123", "%another_variable%": "456"},
"%variable%": "123",
"%another_variable%": "456"
},
) )
f2 = File(source_file="src/f2.sh", permissions=0o744) f2 = File(source_file="src/f2.sh", permissions=0o744)
f2.copy_to("/tmp/decman-files/f2.sh", ) f2.copy_to(
"/tmp/decman-files/f2.sh",
)
f3 = File(content="%variable% doesn't work here.", bin_file=True) f3 = File(content="%variable% doesn't work here.", bin_file=True)
f3.copy_to( f3.copy_to(
@@ -46,10 +44,7 @@ f4.copy_to(
}, },
) )
f5 = File(content="%variable% works here.", f5 = File(content="%variable% works here.", bin_file=False, owner=user, group="root")
bin_file=False,
owner=user,
group="root")
f5.copy_to( f5.copy_to(
"/tmp/decman-files/f5.txt", "/tmp/decman-files/f5.txt",
variables={ variables={
+8 -18
View File
@@ -1,40 +1,31 @@
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
import unittest import unittest
from decman.error import UserFacingError from decman.error import UserFacingError
from decman.lib import Pacman, Store from decman.lib import Pacman, Store
from decman.lib.fpm import ForeignPackageManager, DepGraph, ForeignPackage, ExtendedPackageSearch from decman.lib.fpm import DepGraph, ExtendedPackageSearch, ForeignPackage, ForeignPackageManager
class TestVersionComparisons(unittest.TestCase): class TestVersionComparisons(unittest.TestCase):
def setUp(self): def setUp(self):
pacman = Pacman() pacman = Pacman()
self.pm = ForeignPackageManager(Store(), pacman, self.pm = ForeignPackageManager(Store(), pacman, ExtendedPackageSearch(pacman))
ExtendedPackageSearch(pacman))
def test_should_upgrade_package_returns_true_on_newer_version(self): def test_should_upgrade_package_returns_true_on_newer_version(self):
self.assertTrue( self.assertTrue(self.pm.should_upgrade_package("test", "0.1.9", "0.2.0"))
self.pm.should_upgrade_package("test", "0.1.9", "0.2.0"))
def test_should_upgrade_package_returns_false_on_older_version(self): def test_should_upgrade_package_returns_false_on_older_version(self):
self.assertFalse( self.assertFalse(self.pm.should_upgrade_package("test", "0.1.9", "0.1.8"))
self.pm.should_upgrade_package("test", "0.1.9", "0.1.8"))
def test_should_upgrade_package_returns_false_on_same_version(self): def test_should_upgrade_package_returns_false_on_same_version(self):
self.assertFalse( self.assertFalse(self.pm.should_upgrade_package("test", "0.1.9", "0.1.9"))
self.pm.should_upgrade_package("test", "0.1.9", "0.1.9"))
def test_should_upgrade_package_returns_true_on_devel(self): def test_should_upgrade_package_returns_true_on_devel(self):
self.assertTrue( self.assertTrue(self.pm.should_upgrade_package("test-git", "0", "0", upgrade_devel=True))
self.pm.should_upgrade_package("test-git",
"0",
"0",
upgrade_devel=True))
class TestDepGraph(unittest.TestCase): class TestDepGraph(unittest.TestCase):
def test_add_dependency(self): def test_add_dependency(self):
graph = DepGraph() graph = DepGraph()
@@ -96,8 +87,7 @@ class TestDepGraph(unittest.TestCase):
d = ForeignPackage("D") d = ForeignPackage("D")
d.add_foreign_dependency_packages(["C2"]) d.add_foreign_dependency_packages(["C2"])
self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [c2, b3, v])
[c2, b3, v])
self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [d]) self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [d])
self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [c1]) self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [c1])
self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [b1]) self.assertCountEqual(graph.get_and_remove_outer_dep_pkgs(), [b1])
-1
View File
@@ -1,6 +1,5 @@
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring # pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
from typing import override
import unittest import unittest
from decman import Module, UserPackage from decman import Module, UserPackage
Generated
+131
View File
@@ -0,0 +1,131 @@
version = 1
revision = 3
requires-python = ">=3.13"
[[package]]
name = "certifi"
version = "2025.11.12"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" },
]
[[package]]
name = "charset-normalizer"
version = "3.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" },
{ url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" },
{ url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" },
{ url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" },
{ url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" },
{ url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" },
{ url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" },
{ url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" },
{ url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" },
{ url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" },
{ url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" },
{ url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" },
{ url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" },
{ url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" },
{ url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" },
{ url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" },
{ url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" },
{ url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" },
{ url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" },
{ url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" },
{ url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" },
{ url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" },
{ url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" },
{ url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" },
{ url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" },
{ url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" },
{ url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" },
{ url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" },
{ url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" },
{ url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" },
{ url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" },
{ url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" },
]
[[package]]
name = "decman"
version = "0.4.1"
source = { editable = "." }
dependencies = [
{ name = "requests" },
]
[package.dev-dependencies]
dev = [
{ name = "ruff" },
]
[package.metadata]
requires-dist = [{ name = "requests" }]
[package.metadata.requires-dev]
dev = [{ name = "ruff", specifier = ">=0.14.9" }]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "requests"
version = "2.32.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
]
[[package]]
name = "ruff"
version = "0.14.9"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/1b/ab712a9d5044435be8e9a2beb17cbfa4c241aa9b5e4413febac2a8b79ef2/ruff-0.14.9.tar.gz", hash = "sha256:35f85b25dd586381c0cc053f48826109384c81c00ad7ef1bd977bfcc28119d5b", size = 5809165, upload-time = "2025-12-11T21:39:47.381Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b8/1c/d1b1bba22cffec02351c78ab9ed4f7d7391876e12720298448b29b7229c1/ruff-0.14.9-py3-none-linux_armv6l.whl", hash = "sha256:f1ec5de1ce150ca6e43691f4a9ef5c04574ad9ca35c8b3b0e18877314aba7e75", size = 13576541, upload-time = "2025-12-11T21:39:14.806Z" },
{ url = "https://files.pythonhosted.org/packages/94/ab/ffe580e6ea1fca67f6337b0af59fc7e683344a43642d2d55d251ff83ceae/ruff-0.14.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ed9d7417a299fc6030b4f26333bf1117ed82a61ea91238558c0268c14e00d0c2", size = 13779363, upload-time = "2025-12-11T21:39:20.29Z" },
{ url = "https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d5dc3473c3f0e4a1008d0ef1d75cee24a48e254c8bed3a7afdd2b4392657ed2c", size = 12925292, upload-time = "2025-12-11T21:39:38.757Z" },
{ url = "https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84bf7c698fc8f3cb8278830fb6b5a47f9bcc1ed8cb4f689b9dd02698fa840697", size = 13362894, upload-time = "2025-12-11T21:39:02.524Z" },
{ url = "https://files.pythonhosted.org/packages/31/1c/5b4e8e7750613ef43390bb58658eaf1d862c0cc3352d139cd718a2cea164/ruff-0.14.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa733093d1f9d88a5d98988d8834ef5d6f9828d03743bf5e338bf980a19fce27", size = 13311482, upload-time = "2025-12-11T21:39:17.51Z" },
{ url = "https://files.pythonhosted.org/packages/5b/3a/459dce7a8cb35ba1ea3e9c88f19077667a7977234f3b5ab197fad240b404/ruff-0.14.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a1cfb04eda979b20c8c19550c8b5f498df64ff8da151283311ce3199e8b3648", size = 14016100, upload-time = "2025-12-11T21:39:41.948Z" },
{ url = "https://files.pythonhosted.org/packages/a6/31/f064f4ec32524f9956a0890fc6a944e5cf06c63c554e39957d208c0ffc45/ruff-0.14.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1e5cb521e5ccf0008bd74d5595a4580313844a42b9103b7388eca5a12c970743", size = 15477729, upload-time = "2025-12-11T21:39:23.279Z" },
{ url = "https://files.pythonhosted.org/packages/7a/6d/f364252aad36ccd443494bc5f02e41bf677f964b58902a17c0b16c53d890/ruff-0.14.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd429a8926be6bba4befa8cdcf3f4dd2591c413ea5066b1e99155ed245ae42bb", size = 15122386, upload-time = "2025-12-11T21:39:33.125Z" },
{ url = "https://files.pythonhosted.org/packages/20/02/e848787912d16209aba2799a4d5a1775660b6a3d0ab3944a4ccc13e64a02/ruff-0.14.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab208c1b7a492e37caeaf290b1378148f75e13c2225af5d44628b95fd7834273", size = 14497124, upload-time = "2025-12-11T21:38:59.33Z" },
{ url = "https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a", size = 14195343, upload-time = "2025-12-11T21:39:44.866Z" },
{ url = "https://files.pythonhosted.org/packages/f6/53/3bb8d2fa73e4c2f80acc65213ee0830fa0c49c6479313f7a68a00f39e208/ruff-0.14.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:712ff04f44663f1b90a1195f51525836e3413c8a773574a7b7775554269c30ed", size = 14346425, upload-time = "2025-12-11T21:39:05.927Z" },
{ url = "https://files.pythonhosted.org/packages/ad/04/bdb1d0ab876372da3e983896481760867fc84f969c5c09d428e8f01b557f/ruff-0.14.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a111fee1db6f1d5d5810245295527cda1d367c5aa8f42e0fca9a78ede9b4498b", size = 13258768, upload-time = "2025-12-11T21:39:08.691Z" },
{ url = "https://files.pythonhosted.org/packages/40/d9/8bf8e1e41a311afd2abc8ad12be1b6c6c8b925506d9069b67bb5e9a04af3/ruff-0.14.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8769efc71558fecc25eb295ddec7d1030d41a51e9dcf127cbd63ec517f22d567", size = 13326939, upload-time = "2025-12-11T21:39:53.842Z" },
{ url = "https://files.pythonhosted.org/packages/f4/56/a213fa9edb6dd849f1cfbc236206ead10913693c72a67fb7ddc1833bf95d/ruff-0.14.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:347e3bf16197e8a2de17940cd75fd6491e25c0aa7edf7d61aa03f146a1aa885a", size = 13578888, upload-time = "2025-12-11T21:39:35.988Z" },
{ url = "https://files.pythonhosted.org/packages/33/09/6a4a67ffa4abae6bf44c972a4521337ffce9cbc7808faadede754ef7a79c/ruff-0.14.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7715d14e5bccf5b660f54516558aa94781d3eb0838f8e706fb60e3ff6eff03a8", size = 14314473, upload-time = "2025-12-11T21:39:50.78Z" },
{ url = "https://files.pythonhosted.org/packages/12/0d/15cc82da5d83f27a3c6b04f3a232d61bc8c50d38a6cd8da79228e5f8b8d6/ruff-0.14.9-py3-none-win32.whl", hash = "sha256:df0937f30aaabe83da172adaf8937003ff28172f59ca9f17883b4213783df197", size = 13202651, upload-time = "2025-12-11T21:39:26.628Z" },
{ url = "https://files.pythonhosted.org/packages/32/f7/c78b060388eefe0304d9d42e68fab8cffd049128ec466456cef9b8d4f06f/ruff-0.14.9-py3-none-win_amd64.whl", hash = "sha256:c0b53a10e61df15a42ed711ec0bda0c582039cf6c754c49c020084c55b5b0bc2", size = 14702079, upload-time = "2025-12-11T21:39:11.954Z" },
{ url = "https://files.pythonhosted.org/packages/26/09/7a9520315decd2334afa65ed258fed438f070e31f05a2e43dd480a5e5911/ruff-0.14.9-py3-none-win_arm64.whl", hash = "sha256:8e821c366517a074046d92f0e9213ed1c13dbc5b37a7fc20b07f79b64d62cc84", size = 13744730, upload-time = "2025-12-11T21:39:29.659Z" },
]
[[package]]
name = "urllib3"
version = "2.6.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" },
]