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

Expressions, Parameters and Renderers #55

Merged
merged 108 commits into from
Sep 30, 2024

Conversation

philtweir
Copy link
Contributor

✨ Feature

Description

Please describe and motivate the new feature you've implemented.

Changes Made

Workflows

  • Arbitrarily nested fields
  • Support for sympy expressions
  • All references as sympy Symbols
  • Surfacing deeply nested references
  • Standardize on workflow (removes subworkflow and nested_step)
  • Significant speedups for complex workflows (10-100x)
  • Better support for schema types (TypedDict, dataclasses and attrs)

Renderers

  • Renderers can load from a package
  • Some tests for renderer loading
  • CWL: support for expressions
  • CWL: improvements to type support

Additional Information

Still TODO (help appreciated):

  • update the documentation to match (examples are updated, but not text)
  • docstrings for tests

Checklist

  • I have included a detailed description of the feature.
  • I have provided relevant documentation for my feature.
  • I have added the necessary tests.

Who can review?

@KamenDimitrov97 @elleryames

Copy link
Contributor Author

@philtweir philtweir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add change notes

.github/workflows/python-test-ci.yml Show resolved Hide resolved
docs/quickstart.md Show resolved Hide resolved
src/dewret/__main__.py Show resolved Hide resolved
except Exception as exc:
import traceback

print(exc, exc.__cause__, exc.__context__)
traceback.print_exc()
else:
if pretty:
yaml.dump(cwl, sys.stdout, indent=2)
if len(rendered) == 1:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: Write logic for outputting different formats

src/dewret/annotations.py Show resolved Hide resolved

@classmethod
def assimilate(cls, left: Workflow, right: Workflow) -> "Workflow":
def assimilate(cls, *workflow_args: Workflow) -> "Workflow":
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: improve performance by merging n workflows together simultaneously, instead of pairwise (for example, if we had a function with 5 step results as input arguments, this would get called 4 times, but now gets called once)

@@ -657,14 +765,206 @@ def __workflow__(self) -> Workflow:
...


class Reference:
"""Superclass for all symbolic references to values."""
class FieldableProtocol(Protocol):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: a mixin for structures that use fields

but ParameterReferences are specific, this carries the actual workflow reference.

Returns:
Workflow that the referee is related to.
"""

parameter: Parameter[RawType]
__workflow__: Workflow
class ParameterReferenceMetadata(Generic[T]):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: moving metadata (more) into an attribute called _ for step and parameter references.


U = TypeVar("U")
class IterableParameterReference(IterableMixin[U], ParameterReference[U]):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: add iterable references to handle things we can loop through or index into

@@ -1179,3 +1633,104 @@ def is_task(task: Lazy) -> bool:
True if `task` is indeed a task.
"""
return isinstance(task, LazyEvaluation)

def expr_to_references(expression: Any, remap: Callable[[Any], Any] | None = None) -> tuple[ExprType, list[Reference[Any] | Parameter[Any]]]:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change: add support for sympy expressions involving references.

Copy link
Contributor

@KamenDimitrov97 KamenDimitrov97 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some comments with change suggestions.
I still have a few files to review and test out.

render_module = cast(BaseRenderModule, module)
else:
render_module = renderer
if not isinstance(render_module, StructuredRenderModule):
Copy link
Contributor

@KamenDimitrov97 KamenDimitrov97 Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit hard to follow.

This is understandable

# This is understandable
    render_module: BaseRenderModule
    if isinstance(renderer, Path):
        if (render_dir := str(renderer.parent)) not in sys.path:
            sys.path.append(render_dir)

        # Attempt to load renderer as package, falling back to a single module otherwise.
        # This enables relative imports in renderers and therefore the ability to modularize.
        module = load_module_or_package("__renderer__", renderer)
        sys.modules["__renderer_mod__"] = module
        render_module = cast(BaseRenderModule, module)
    else:
        render_module = renderer

But can we handle conditionals here in a similar way to this?
Since the renderer has to be Path | RawRenderModule | StructuredRenderModule and StructuredRenderModule is a BaseRenderModule
and when we render_module = cast(BaseRenderModule, module)

I can see what every modules implementation is without having to think about it.
My first thought was what happened to the BaseRenderModule

    if isinstance(render_module, RawRenderModule):
        return render_module.render_raw
    elif isinstance(render_module, (StructuredRenderModule)):
        def _render(workflow: Workflow, render_module: StructuredRenderModule, pretty: bool=False, **kwargs: RenderConfiguration) -> dict[str, str]:
            rendered = render_module.render(workflow, **kwargs)
            return {
                key: structured_to_raw(value, pretty=pretty)
                for key, value in rendered.items()
            }

        return cast(RenderCall, partial(_render, render_module=render_module, pretty=pretty))

raise NotImplementedError("This render module neither seems to be a structured nor a raw render module.")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KamenDimitrov97 To solve

src/dewret/render.py Outdated Show resolved Hide resolved
.github/workflows/python-test-ci.yml Show resolved Hide resolved
src/dewret/core.py Outdated Show resolved Hide resolved
metadata += list(parent_metadata)
return parent_type, tuple(metadata)

RenderConfiguration = dict[str, RawType]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly irrelevant ignore if it is but maybe a comment like this

# Configuration settings for the renderer

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KamenDimitrov97 keep
Generic type for configuration settings for the renderer

src/dewret/backends/backend_dask.py Show resolved Hide resolved
If `fn` is a class, it takes the constructor, and if it is a method, it takes
the `__func__` attribute.
"""
self.fn = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know how I feel about these.

if inspect.isclass(fn):
    self.fn = fn.__init__
elif inspect.ismethod(fn):
    self.fn = fn.__func__
else:
    self.fn = fn

It might be a lack of years of experience with python but it just seems harder to read

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KamenDimitrov97 change this

from dewret.workflow import Lazy
from dewret.workflow import Reference, Raw, Workflow, Step, Task
from dewret.utils import BasicType
from dewret.workflow import Reference, Workflow, Step, Task

RawType = typing.Union[BasicType, list[str], list["RawType"], dict[str, "RawType"]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up comment: We should also update the documentations and tutorials to use | instead of 'union'

docs/renderer_tutorial.md Show resolved Hide resolved
src/dewret/__main__.py Show resolved Hide resolved
if len(rendered) == 1:
with opener("", "w") as output_f:
output_f.write(rendered["__root__"])
elif "%" in output:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep this in outside the utility function

src/dewret/__main__.py Show resolved Hide resolved
assert entry.relline == 2
assert "attempted relative import with no known parent package" in str(exc.value)

nonfrender_py = Path(__file__).parent / "_lib/nonfrender.py"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing module runtime check against protocol.

@KamenDimitrov97 KamenDimitrov97 force-pushed the feature/parameter-type-improvements branch from f9b3e8c to b6677c0 Compare September 17, 2024 11:20
@KamenDimitrov97 KamenDimitrov97 force-pushed the feature/parameter-type-improvements branch from ee37865 to 2fa4145 Compare September 30, 2024 12:13
@KamenDimitrov97 KamenDimitrov97 force-pushed the feature/parameter-type-improvements branch from 2fa4145 to 3b9ccfe Compare September 30, 2024 12:15
@KamenDimitrov97 KamenDimitrov97 merged commit 229102b into release/0.10.0 Sep 30, 2024
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants