diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..d9e60cc --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,37 @@ +name: Unit tests + +on: + - push + - pull_request + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13"] + + steps: + - uses: actions/checkout@v4 + - uses: mamba-org/setup-micromamba@v2 + with: + environment-file: environment.yml + init-shell: >- + bash + cache-environment: false + create-args: python=${{ matrix.python-version }} + post-cleanup: 'all' + - name: Install xcengine + shell: bash -el {0} + run: | + python --version + pip install --no-deps --editable . + - name: Run pytest + shell: bash -el {0} + run: | + pytest --cov=xcengine --cov-branch --cov-report=xml + - uses: codecov/codecov-action@v5 + with: + verbose: true diff --git a/README.md b/README.md index bcef678..02fc0da 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Unit tests](https://github.com/xcube-dev/xcengine/actions/workflows/tests.yaml/badge.svg)](https://github.com/xcube-dev/xcengine/actions/workflows/tests.yaml) + # xcengine: turn Jupyter notebooks into Application Packages xcengine provides tools to convert a Jupyter notebook into one of several diff --git a/environment.yml b/environment.yml index 2fa23e1..04b64b8 100644 --- a/environment.yml +++ b/environment.yml @@ -3,17 +3,19 @@ channels: - conda-forge dependencies: # Python - - python >=3.10 + - python >=3.11 # Required - click - docker-py - ipython # Used by nbconvert to transform IPython syntax to pure Python - nbconvert - nbformat + - pystac - pyyaml - xcube # See note below # test dependencies - pytest + - pytest-cov # Note: xcube is not required for the conversion itself, but is required # to run generated scripts outside containers ("create" mode). diff --git a/xcengine/parameters.py b/xcengine/parameters.py index 621067f..5a19c75 100644 --- a/xcengine/parameters.py +++ b/xcengine/parameters.py @@ -38,6 +38,8 @@ def make_cwl_params(self): def from_code(cls, code: str) -> "NotebookParameters": # TODO run whole notebook up to params cell, not just the params cell! # (Because it might use imports etc. from earlier in the notebook.) + # This will need some tweaking of the parameter extraction -- see + # comment therein. return cls(cls.extract_variables(code)) @classmethod @@ -60,10 +62,11 @@ def from_yaml_file(cls, path: str | os.PathLike) -> "NotebookParameters": @classmethod def extract_variables(cls, code: str) -> dict[str, tuple[type, Any]]: - _old_locals = set(locals().keys()) - exec(code) - newvars = locals().keys() - _old_locals - {"_old_locals"} - return {k: cls.make_param_tuple(locals()[k]) for k in newvars} + # TODO: just working on a single code block is insufficient: + # We should execute everything up to the params cell, but only extract + # variables defined in the params cell. + exec(code, globals(), locals_ := {}) + return {k: cls.make_param_tuple(locals_[k]) for k in (locals_.keys())} @staticmethod def make_param_tuple(value: Any) -> tuple[type, Any]: @@ -98,7 +101,7 @@ def get_cwl_workflow_input(self, var_name: str) -> dict[str, Any]: def get_cwl_commandline_input(self, var_name: str) -> dict[str, Any]: return self.get_cwl_workflow_input(var_name) | { - "inputBinding": {"prefix": f"--{var_name.replace("_", "-")}"} + "inputBinding": {"prefix": f'--{var_name.replace("_", "-")}'} } def to_yaml(self) -> str: