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
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ docs/_build/
.pybuilder/
target/


# IPython
profile_default/
ipython_config.py
Expand Down Expand Up @@ -153,4 +152,6 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/

WIP/
283 changes: 211 additions & 72 deletions src/kapla/projects/kproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import (
TYPE_CHECKING,
Any,
DefaultDict,
Dict,
Iterable,
Iterator,
Expand All @@ -27,6 +28,7 @@
from kapla.specs.pyproject import (
DEFAULT_BUILD_SYSTEM,
Dependency,
DependencyMeta,
Group,
PoetryConfig,
PyProjectSpec,
Expand Down Expand Up @@ -189,99 +191,236 @@ def get_build_dependencies(
dependencies: Dict[str, Dependency] = {}
groups: Dict[str, Group] = {}
extras: Dict[str, List[str]] = {}
# Fetch constraints

constraints: Dict[str, str]
if self.repo is None:
constraints = defaultdict(lambda: "*")
else:
constraints = self.repo.get_packages_constraints()
# Iterate over dependencies and replace version

self._process_main_dependencies(dependencies, constraints, lock_versions)
self._process_extras_and_groups(
dependencies, extras, groups, constraints, lock_versions
)
self._handle_python_dependency(dependencies, include_python)
self._remove_local_dependencies(dependencies, include_local)
return dependencies, extras, groups

def _process_main_dependencies(
self,
dependencies: Dict[str, Dependency],
constraints: Union[DefaultDict[str, str], Dict[str, str]],
lock_versions: bool,
) -> None:
for dep in self.spec.dependencies:
if isinstance(dep, str):
if lock_versions:
locked_version = self.get_locked_version(dep)
else:
locked_version = constraints.get(dep, "*")
dependencies[dep] = Dependency(version=locked_version)
else:
for key, value in dep.items():
if lock_versions:
locked_version = self.get_locked_version(key)
else:
locked_version = constraints.get(key, "*")
dependencies[key] = Dependency.parse_obj(
{
**value.dict(exclude_unset=True, by_alias=True),
"version": locked_version,
}
self._lock_and_store_dependencies(
lock_versions, dependencies, constraints, dep
)
local_dependencies = self._get_local_dependency_names(dep)
self._process_secondary_dependencies(
local_dependencies, dependencies, constraints, lock_versions
)

def _get_local_dependency_names(
self, dep: Union[str, Dict[str, DependencyMeta]]
) -> List[str]:
"""Extract dependency names from a dependency specification."""
return [dep] if isinstance(dep, str) else list(dep.keys())

def _process_secondary_dependencies(
self,
local_dependencies: List[str],
dependencies: Dict[str, Dependency],
constraints: Union[DefaultDict[str, str], Dict[str, str]],
lock_versions: bool,
) -> None:
"""Process secondary dependencies of the given local dependencies."""
if not self.repo:
return

for dep_name in local_dependencies:
locked_package = self.repo.packages_lock.packages.get(dep_name)
if locked_package and locked_package.dependencies:
visited: Set[str] = set()
for secondary_dep in locked_package.dependencies:
self._process_dependency_tree(
secondary_dep, visited, dependencies, constraints, lock_versions
)
# Iterate over extra dependencies and replace version

def _process_dependency_tree(
self,
dep_name: str,
visited: Set[str],
dependencies: Dict[str, Dependency],
constraints: Union[DefaultDict[str, str], Dict[str, str]],
lock_versions: bool,
) -> None:
"""Process a dependency and its subdependencies recursively."""
if dep_name in visited:
return
visited.add(dep_name)

self._lock_and_store_dependencies(
lock_versions, dependencies, constraints, dep_name
)

if not self.repo:
return

locked_package = self.repo.packages_lock.packages.get(dep_name)
if locked_package and locked_package.dependencies:
for sub_dep in locked_package.dependencies:
self._process_dependency_tree(
sub_dep, visited, dependencies, constraints, lock_versions
)

def _process_extras_and_groups(
self,
dependencies: Dict[str, Dependency],
extras: Dict[str, List[str]],
groups: Dict[str, Group],
constraints: Union[DefaultDict[str, str], Dict[str, str]],
lock_versions: bool,
) -> None:
for group_name, group_dependencies in self.spec.extras.items():
# Let's create a group and an extra
groups[group_name] = Group(dependencies={})
extras[group_name] = []
# Iterate over dependencies and replace version
visited: Set[str] = set()
for dep in group_dependencies:
if isinstance(dep, str):
if lock_versions:
locked_version = self.get_locked_version(dep)
else:
locked_version = constraints.get(dep, "*")
# Add dependency to group
groups[group_name].dependencies[dep] = Dependency(
version=locked_version
self._add_dependency_to_group(
dep,
group_name,
dependencies,
extras,
groups,
constraints,
lock_versions,
visited,
)
# Add dependency to extra
if dep not in extras[group_name]:
extras[group_name].append(dep)
# Add dependency to optional dependencies
if dep not in dependencies:
dependencies[dep] = Dependency.parse_obj(
{
"version": locked_version,
"optional": True,
}
)
else:
for key, value in dep.items():
if lock_versions:
locked_version = self.get_locked_version(key)
else:
locked_version = constraints.get(key, "*")
# Add dependency to group
groups[group_name].dependencies[key] = Dependency.parse_obj(
{
**value.dict(exclude_unset=True, by_alias=True),
"version": locked_version,
}
self._add_dependency_to_group(
key,
group_name,
dependencies,
extras,
groups,
constraints,
lock_versions,
visited,
value,
)
# Add dependency to extra
if key not in extras[group_name]:
extras[group_name].append(key)
# Add dependency to optional dependencies
if key not in dependencies:
dependencies[key] = Dependency.parse_obj(
{
**value.dict(exclude_unset=True, by_alias=True),
"version": locked_version,
"optional": True,
}
)
# Make sure python dependency is set

def _add_dependency_to_group(
self,
dep_name: str,
group_name: str,
dependencies: Dict[str, Dependency],
extras: Dict[str, List[str]],
groups: Dict[str, Group],
constraints: Union[DefaultDict[str, str], Dict[str, str]],
lock_versions: bool,
visited: Optional[Set[str]] = None,
value: Optional[DependencyMeta] = None,
) -> None:
if visited is None:
visited = set()
if dep_name in visited:
return
visited.add(dep_name)
locked_version = (
self.get_locked_version(dep_name)
if lock_versions
else constraints.get(dep_name, "*")
)
dep_obj = (
Dependency.parse_obj(
{
**value.dict(exclude_unset=True, by_alias=True),
"version": locked_version,
}
)
if value
else Dependency(version=locked_version)
)
groups[group_name].dependencies[dep_name] = dep_obj
if dep_name not in extras[group_name]:
extras[group_name].append(dep_name)
if dep_name not in dependencies:
dep_dict = (
{
**value.dict(exclude_unset=True, by_alias=True),
"version": locked_version,
"optional": True,
}
if value
else {"version": locked_version, "optional": True}
)
dependencies[dep_name] = Dependency.parse_obj(dep_dict)
if self.repo:
locked_package = self.repo.packages_lock.packages.get(dep_name)
if locked_package and locked_package.dependencies:
for sub_dep in locked_package.dependencies:
self._add_dependency_to_group(
sub_dep,
group_name,
dependencies,
extras,
groups,
constraints,
lock_versions,
visited,
)

def _handle_python_dependency(
self,
dependencies: Dict[str, Dependency],
include_python: bool,
) -> None:
if include_python:
if "python" not in dependencies:
if self.repo:
python_dep = self.repo.get_dependency("python")
if python_dep:
dependencies["python"] = python_dep.copy()
if "python" not in dependencies and self.repo:
python_dep = self.repo.get_dependency("python")
if python_dep:
dependencies["python"] = python_dep.copy()
else:
dependencies.pop("python", None)
# Remove local deps

def _remove_local_dependencies(
self,
dependencies: Dict[str, Dependency],
include_local: bool,
) -> None:
if not include_local:
for dep in self.get_local_dependencies_names():
dependencies.pop(dep)
# Return values
return dependencies, extras, groups
dependencies.pop(dep, None)

def _lock_and_store_dependencies(
self,
lock_versions: bool,
dependencies: Dict[str, Dependency],
constraints: Union[DefaultDict[str, str], Dict[str, str]],
dep: Union[str, Dict[str, DependencyMeta]],
) -> None:
if isinstance(dep, str):
if lock_versions:
locked_version = self.get_locked_version(dep)
else:
locked_version = constraints.get(dep, "*")

dependencies[dep] = Dependency(version=locked_version)
else:
for key, value in dep.items():
if lock_versions:
locked_version = self.get_locked_version(key)
else:
locked_version = constraints.get(key, "*")
dependencies[key] = Dependency.parse_obj(
{
**value.dict(exclude_unset=True, by_alias=True),
"version": locked_version,
}
)

def get_locked_version(self, package: str) -> str:
if self.repo:
Expand Down
9 changes: 6 additions & 3 deletions src/kapla/projects/krepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,12 @@ def discover_projects(
for workspace_directory in all_workspaces[name]:
# Find files named "project.yml" or "project.yaml" starting from the workspace
for filepath in find_files_using_gitignore(
("project.yml", "project.yaml", "project_encrypted.yml", "project_encrypted.yaml"),
(
"project.yml",
"project.yaml",
"project_encrypted.yml",
"project_encrypted.yaml",
),
root=workspace_directory,
):
# Create a new instance of KProject
Expand Down Expand Up @@ -513,7 +518,6 @@ async def install_editable_projects(

# Define function to perform install
async def install_project(project: KProject) -> None:
nonlocal results
async with limiter:
cmd = await project.install(
exclude_groups=exclude_groups,
Expand Down Expand Up @@ -604,7 +608,6 @@ async def build_projects(
):
# Define function to perform install
async def build_project(project: KProject) -> None:
nonlocal results
async with limiter:
cmd = await project.build(
env=env,
Expand Down
2 changes: 1 addition & 1 deletion src/kapla/specs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class BuildSystem(AliasedModel):


class Package(AliasedModel):
"""A package can be declared as a dictionnary.
"""A package can be declared as a dictionary.

References:
* <https://python-poetry.org/docs/pyproject/#packages>
Expand Down
6 changes: 3 additions & 3 deletions src/kapla/specs/kproject.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from .base import AliasedModel
from .common import BasePythonConfig
from .pyproject import DepedencyMeta
from .pyproject import DependencyMeta


class DockerImageSpec(AliasedModel):
Expand All @@ -26,7 +26,7 @@ class DockerSpec(AliasedModel):
class KProjectSpec(BasePythonConfig):
# Docs: <https://python-poetry.org/docs/pyproject/#version>
version: Optional[str] = None
dependencies: List[Union[str, Dict[str, DepedencyMeta]]] = []
dependencies: List[Union[str, Dict[str, DependencyMeta]]] = []
docker: Optional[DockerSpec] = None
# Docs: <https://python-poetry.org/docs/pyproject/#extras>
extras: Dict[str, List[Union[str, Dict[str, DepedencyMeta]]]] = {}
extras: Dict[str, List[Union[str, Dict[str, DependencyMeta]]]] = {}
Loading