Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project cleanup, added Requirement.py #1

Merged
merged 1 commit into from
Apr 26, 2024
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
14 changes: 12 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ classifiers = [
]

dependencies = [
# TODO
"dbrownell_Common",
"pluggy == 1.*",
"typer ~= 0.9"
]

Expand Down Expand Up @@ -72,7 +73,6 @@ Repository = "https://github.com/gt-sse-center/RepoAuditor"
[project.scripts]
RepoAuditor = "RepoAuditor:EntryPoint.app"


# ----------------------------------------------------------------------
# |
# | black
Expand All @@ -81,6 +81,16 @@ RepoAuditor = "RepoAuditor:EntryPoint.app"
[tool.black]
line-length = 100

# ----------------------------------------------------------------------
# |
# | coverage
# |
# ----------------------------------------------------------------------
[tool.coverage.run]
omit = [
"*/Impl/*",
]

# ----------------------------------------------------------------------
# |
# | pylint
Expand Down
47 changes: 5 additions & 42 deletions src/RepoAuditor/EntryPoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

from typer.core import TyperGroup # type: ignore [import-untyped]

from RepoAuditor import Math, __version__
from RepoAuditor import __version__


# ----------------------------------------------------------------------
Expand All @@ -34,47 +34,10 @@ def list_commands(self, *args, **kwargs): # pylint: disable=unused-argument


# ----------------------------------------------------------------------
@app.command("Add")
def Add(
x: int,
y: int,
) -> None:
"""Adds 2 values."""

sys.stdout.write(str(Math.Add(x, y)))


# ----------------------------------------------------------------------
@app.command("Sub")
def Sub(
x: int,
y: int,
) -> None:
"""Subtracts 2 values."""

sys.stdout.write(str(Math.Sub(x, y)))


# ----------------------------------------------------------------------
@app.command("Mult")
def Mult(
x: int,
y: int,
) -> None:
"""Multiplies 2 values."""

sys.stdout.write(str(Math.Mult(x, y)))


# ----------------------------------------------------------------------
@app.command("Div")
def Div(
x: int,
y: int,
) -> None:
"""Divides 1 value by another."""

sys.stdout.write(str(Math.Div(x, y)))
@app.command("Placeholder")
def Placeholder() -> None:
"""This is a placeholder command that should be removed once actual functionality is added."""
pass


# ----------------------------------------------------------------------
Expand Down
121 changes: 121 additions & 0 deletions src/RepoAuditor/Impl/ParallelSequentialProcessor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -------------------------------------------------------------------------------
# | |
# | Copyright (c) 2024 Scientific Software Engineering Center at Georgia Tech |
# | Distributed under the MIT License. |
# | |
# -------------------------------------------------------------------------------
"""Contains functionality to process items in parallel and/or sequentially."""

from enum import auto, Enum
from typing import Callable, cast, Optional, TypeVar

from dbrownell_Common import ExecuteTasks # type: ignore[import-untyped]
from dbrownell_Common.Streams.DoneManager import DoneManager # type: ignore[import-untyped]
from dbrownell_Common.Streams.StreamDecorator import StreamDecorator # type: ignore[import-untyped]


# ----------------------------------------------------------------------
class ExecutionStyle(Enum):
"""Controls the way in which a Requirement, Query, and Module can be processed."""

Sequential = auto()
Parallel = auto()


# ----------------------------------------------------------------------
ItemType = TypeVar("ItemType")
OutputType = TypeVar("OutputType")


def ParallelSequentialProcessor(
items: list[ItemType],
calculate_result_func: Callable[[ItemType], tuple[int, OutputType]],
dm: Optional[DoneManager] = None,
*,
max_num_threads: Optional[int] = None,
) -> list[OutputType]:
if dm is None:
with DoneManager.Create(StreamDecorator(None), "", line_prefix="") as dm:
return _Impl(
dm,
items,
calculate_result_func,
max_num_threads,
)

return _Impl(
dm,
items,
calculate_result_func,
max_num_threads,
)


# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
# ----------------------------------------------------------------------
def _Impl(
dm: DoneManager,
items: list[ItemType],
calculate_result_func: Callable[[ItemType], tuple[int, OutputType]],
max_num_threads: Optional[int],
) -> list[OutputType]:
# Divide the items into those that can be run in parallel and those that must be run sequentially
parallel: list[tuple[int, ItemType]] = []
sequential: list[tuple[int, ItemType]] = []

for index, item in enumerate(items):
execution_style = item.style # type: ignore

if execution_style == ExecutionStyle.Parallel:
parallel.append((index, item))
elif execution_style == ExecutionStyle.Sequential:
sequential.append((index, item))
else:
assert False, execution_style # pragma: no cover

if len(parallel) == 1:
sequential.append(parallel[0])
parallel = []

# Calculate the results
results: list[Optional[OutputType]] = [None] * len(items)

# ----------------------------------------------------------------------
def Execute(
results_index: int,
item: ItemType,
) -> ExecuteTasks.TransformResultComplete:
return_code, result = calculate_result_func(item)

assert results[results_index] is None
results[results_index] = result

return ExecuteTasks.TransformResultComplete(None, return_code)

# ----------------------------------------------------------------------

if parallel:
ExecuteTasks.TransformTasks(
dm,
"Processing",
[
ExecuteTasks.TaskData(item.name, (results_index, item)) # type: ignore
for results_index, item in parallel
],
lambda context, status: Execute(*context),
max_num_threads=max_num_threads,
)

for sequential_index, (results_index, item) in enumerate(sequential):
with dm.Nested(
"Processing '{}' ({} of {})...".format(
item.name, # type: ignore
sequential_index + 1 + len(parallel),
len(items),
),
):
Execute(results_index, item)

assert not any(result is None for result in results), results
return cast(list[OutputType], results)
Empty file.
27 changes: 0 additions & 27 deletions src/RepoAuditor/Math.py

This file was deleted.

94 changes: 94 additions & 0 deletions src/RepoAuditor/Requirement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# -------------------------------------------------------------------------------
# | |
# | Copyright (c) 2024 Scientific Software Engineering Center at Georgia Tech |
# | Distributed under the MIT License. |
# | |
# -------------------------------------------------------------------------------
"""Contains types used when creating Requirements."""

from abc import ABC, abstractmethod
from dataclasses import dataclass
from enum import auto, Enum
from typing import Any, Optional


from .Impl.ParallelSequentialProcessor import ExecutionStyle


# ----------------------------------------------------------------------
class EvaluateResult(Enum):
"""Result of evaluating a Requirement against a set of data."""

DoesNotApply = auto()
Success = auto()
Warning = auto()
Error = auto()


# ----------------------------------------------------------------------
@dataclass(frozen=True)
class Requirement(ABC):
"""A single requirement that can be evaluated against a set of data."""

# ----------------------------------------------------------------------
# |
# | Public Types
# |
# ----------------------------------------------------------------------
@dataclass(frozen=True)
class EvaluateInfo:
"""Information associated with evaluating a Requirement against a set of data."""

result: EvaluateResult
context: Optional[str]

resolution: Optional[str]
rationale: Optional[str]

requirement: "Requirement"

# ----------------------------------------------------------------------
# |
# | Public Data
# |
# ----------------------------------------------------------------------
name: str
description: str
style: ExecutionStyle

resolution_template: str
rationale_template: str

# ----------------------------------------------------------------------
# |
# | Public Methods
# |
# ----------------------------------------------------------------------
def Evaluate(
self,
query_data: dict[str, Any],
) -> "Requirement.EvaluateInfo":
result, context = self._EvaluateImpl(query_data)

if result in [EvaluateResult.DoesNotApply, EvaluateResult.Success]:
return Requirement.EvaluateInfo(result, context, None, None, self)

return Requirement.EvaluateInfo(
result,
context,
self.resolution_template.format(**query_data),
self.rationale_template.format(**query_data),
self,
)

# ----------------------------------------------------------------------
# |
# | Private Methods
# |
# ----------------------------------------------------------------------
@abstractmethod
def _EvaluateImpl(
self,
query_data: dict[str, Any],
) -> tuple[EvaluateResult, Optional[str]]:
"""Perform the actual evaluation"""
2 changes: 0 additions & 2 deletions src/RepoAuditor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,3 @@
# on changes observed in the git repository. The default value below will be used until the value
# here is explicitly updated as part of a commit.
__version__ = "0.1.0"

from .Math import Add, Sub, Mult, Div
Loading
Loading