-
Notifications
You must be signed in to change notification settings - Fork 1
feat: SDK - pipeline run parameters - dynamic choices (HEXA-1620) #385
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
Open
mrivar
wants to merge
19
commits into
HEXA-1620-parameters-refactor
Choose a base branch
from
HEXA-1620-parameter-dynamic-choices
base: HEXA-1620-parameters-refactor
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
0c295d5
refactor: parameters into more atomic files
mrivar 3d5b011
feature: dynamic choices using FileChoices - first iteration
mrivar f796235
fix: lint
mrivar 1e22fac
refactor: rename ChoicesFromFile
mrivar 9e123f3
fix: lint
mrivar 5e1f110
fix: lint dosctrings
mrivar 556bd89
feat: improve ast for callables
mrivar e0323ca
feat: add string shorthand choices
mrivar 13a0041
fix: lint
mrivar 4f97ab0
feat: minor improvements
mrivar 08b13e5
feat: minor minor improvements
mrivar 9cfaa5c
feat: add docstrings
mrivar 71fd0c3
recover comment
mrivar c39a213
typo
mrivar 7e6f234
Merge branch 'HEXA-1620-parameters-refactor' into HEXA-1620-parameter…
mrivar 1066079
fix: conflict
mrivar 85ad208
feat: remove auto detect format
mrivar 3807a30
tests: fix tests
mrivar 16f56af
fix: conda
mrivar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| l lint: | ||
| @echo "Executing lint in backend code (pre-commit)" | ||
| pre-commit run --show-diff-on-failure --color=always --all-files |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| """Mixin for classes that can reconstruct themselves from an AST Call node.""" | ||
|
|
||
| import ast | ||
| import inspect | ||
|
|
||
|
|
||
| class AstConstructible: | ||
| """Mixin that enables reconstruction of a class instance from an AST Call node. | ||
|
|
||
| Any class whose ``__init__`` takes only scalar (``ast.Constant``) arguments | ||
| can inherit from this mixin and get ``from_ast_call`` for free. Adding or | ||
| renaming ``__init__`` parameters does *not* require touching the parser. | ||
|
|
||
| To make the AST parser recognise a new subclass by name, add one entry to | ||
| ``_AST_CALLABLE_TYPES`` in ``runtime.py`` (and ensure the subclass module is | ||
| imported there). Auto-registration via ``__init_subclass__`` would not remove | ||
| that requirement — the registry entry only exists after the module is imported, | ||
| so an explicit import would still be needed. | ||
| """ | ||
|
|
||
| @classmethod | ||
| def from_ast_call(cls, node: ast.Call) -> "AstConstructible": | ||
| """Reconstruct an instance from an AST Call node. | ||
|
|
||
| Maps positional args to ``__init__`` parameter names via | ||
| ``inspect.signature``, then merges keyword args, and calls ``cls``. | ||
| """ | ||
| param_names = list(inspect.signature(cls).parameters.keys()) | ||
| kwargs = {} | ||
| for i, arg in enumerate(node.args): | ||
| if i >= len(param_names): | ||
| break | ||
| if not isinstance(arg, ast.Constant): | ||
| raise ValueError( | ||
| f"{cls.__name__}() positional argument {i + 1} must be a literal value, not a dynamic expression." | ||
| ) | ||
| kwargs[param_names[i]] = arg.value | ||
| for kw in node.keywords: | ||
| if not isinstance(kw.value, ast.Constant): | ||
| raise ValueError( | ||
| f"{cls.__name__}() keyword argument '{kw.arg}' must be a literal value, not a dynamic expression." | ||
| ) | ||
| kwargs[kw.arg] = kw.value.value | ||
| return cls(**kwargs) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| """Dynamic choices classes for pipeline parameters.""" | ||
|
|
||
| from openhexa.sdk.pipelines.exceptions import InvalidParameterError | ||
|
|
||
| from .ast_constructible import AstConstructible | ||
|
|
||
| _SUPPORTED_FORMATS = {"csv", "json", "yaml", "yml"} | ||
|
|
||
|
|
||
| class ChoicesFromFile(AstConstructible): | ||
| """Descriptor for choices loaded dynamically from a file in the workspace file system. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| path : str | ||
| Path to the file in the workspace file system (e.g. "data/districts.csv"). | ||
| column : str, optional | ||
| Column name (CSV) or key (JSON/YAML) to use as choice values. | ||
| Required when the file has more than one column/key. | ||
| format : str, optional | ||
| File format (e.g. "csv", "json", "yaml"). Sent as-is to the platform. | ||
| """ | ||
|
|
||
| def __init__(self, path: str, column: str | None = None, format: str | None = None): | ||
| self.path = path | ||
| self.column = column | ||
| self.format = format | ||
| self._validate_spec() | ||
|
|
||
| def _validate_spec(self): | ||
| """Validate the path and column specification.""" | ||
| if not self.path or not isinstance(self.path, str): | ||
| raise InvalidParameterError("ChoicesFromFile path must be a non-empty string.") | ||
| if self.column is not None and not isinstance(self.column, str): | ||
| raise InvalidParameterError("ChoicesFromFile column must be a string.") | ||
| if self.format is not None and self.format not in _SUPPORTED_FORMATS: | ||
| raise InvalidParameterError( | ||
| f"ChoicesFromFile format '{self.format}' is not supported. " | ||
| f"Supported formats: {', '.join(sorted(_SUPPORTED_FORMATS))}." | ||
| ) | ||
|
|
||
| def __repr__(self) -> str: | ||
| """Return a string representation of the ChoicesFromFile instance.""" | ||
| parts = [repr(self.path)] | ||
| if self.column is not None: | ||
| parts.append(f"column={self.column!r}") | ||
| if self.format is not None: | ||
| parts.append(f"format={self.format!r}") | ||
| return f"ChoicesFromFile({', '.join(parts)})" | ||
|
|
||
| def __eq__(self, other: object) -> bool: | ||
| """Check equality based on path, column, and format.""" | ||
| if not isinstance(other, ChoicesFromFile): | ||
| return NotImplemented | ||
| return self.path == other.path and self.column == other.column and self.format == other.format | ||
|
|
||
| def __hash__(self) -> int: | ||
| """Return hash based on path, column, and format.""" | ||
| return hash((self.path, self.column, self.format)) | ||
|
|
||
| def to_dict(self) -> dict: | ||
| """Return a dictionary representation of the choices spec.""" | ||
| return { | ||
| "format": self.format, | ||
| "path": self.path, | ||
| "column": self.column, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed because we were getting an error on conda build:
https://github.com/BLSQ/openhexa-sdk-python/actions/runs/25662804662/job/75327513034
conda-forge recently published requests 2.34.1, which didn't exist when the previous pin was written