Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.ruff_cache
.vscode

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
13 changes: 5 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,14 @@ docs = ["pdoc"]
styx = "styx.main:main"

[tool.pytest.ini_options]
pythonpath = [
"src"
]
pythonpath = ["src"]

[tool.mypy]
ignore_missing_imports = true

[tool.ruff]
preview = true
extend-exclude = [
"examples",
"src/styx/boutiques/model.py"
]
extend-exclude = ["examples", "src/styx/boutiques/model.py"]
line-length = 120
indent-width = 4
src = ["src"]
Expand All @@ -62,7 +57,9 @@ unfixable = []
convention = "google"

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = []
"tests/**/*.py" = [
"ANN401" # Dynamic type expression ('Any')
]

[build-system]
requires = ["poetry-core>=1.2.0"]
Expand Down
11 changes: 11 additions & 0 deletions src/styx/ir/pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@
),
")",
])
if hasattr(obj, "__dict__"):
return f"\n{_indentation(ind)}".join([

Check warning on line 72 in src/styx/ir/pretty_print.py

View check run for this annotation

Codecov / codecov/patch

src/styx/ir/pretty_print.py#L71-L72

Added lines #L71 - L72 were not covered by tests
f"{obj.__class__.__name__}(",
*_expand(
",\n".join([
f" {field_name}={_pretty_print(field_value, 1)}"
for field_name, field_value in obj.__dict__.items()
])
),
")",
])
else:
return str(obj)

Expand Down
37 changes: 37 additions & 0 deletions tests/frontend/boutiques/test_command_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import styx.ir.core as ir
from styx.frontend.boutiques.core import from_boutiques


class TestCommandLine:
package_name = "My package"
descriptor_name = "My descriptor"

def test_basic(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": "hello world",
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
assert isinstance(groups[0].cargs[0].tokens[0], str)
assert groups[0].cargs[0].tokens[0] == "hello"
assert isinstance(groups[1].cargs[0].tokens[0], str)
assert groups[1].cargs[0].tokens[0] == "world"

def test_quoting(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": 'hello "string with whitespace"',
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
assert isinstance(groups[0].cargs[0].tokens[0], str)
assert groups[0].cargs[0].tokens[0] == "hello"
assert isinstance(groups[1].cargs[0].tokens[0], str)
assert groups[1].cargs[0].tokens[0] == "string with whitespace"
27 changes: 27 additions & 0 deletions tests/frontend/boutiques/test_descriptor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from typing import Any

import pytest

import styx.ir.core as ir
from styx.frontend.boutiques.core import from_boutiques


class TestDescriptor:
package_name = "My package"
descriptor_name = "My descriptor"

def test_descriptor(self) -> None:
bt = {"name": self.descriptor_name}
out = from_boutiques(bt, self.package_name)

assert isinstance(out, ir.Interface)
assert out.package.name == self.package_name
assert isinstance(out.command.body, ir.Param.Struct)
assert out.command.base.name == self.descriptor_name

@pytest.mark.parametrize("descriptor_name", (123, ["list of str"]))
@pytest.mark.skip
def test_invalid_descriptor(self, descriptor_name: Any) -> None:
bt = {"name": descriptor_name}
with pytest.raises(TypeError):
from_boutiques(bt, self.package_name)
60 changes: 60 additions & 0 deletions tests/frontend/boutiques/test_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from typing import Any

import pytest

import styx.ir.core as ir
from styx.frontend.boutiques.core import from_boutiques


class TestDocumentation:
# NOTE: 'literature' unable to be tested
package_name = "My package"
descriptor_name = "My descriptor"

@pytest.mark.parametrize("desc", ("A short description", None))
def test_valid_description(self, desc: str | None) -> None:
bt = {"name": self.descriptor_name, "description": desc}
out = from_boutiques(bt, self.package_name)
assert isinstance(out.command.base.docs, ir.Documentation)
assert out.command.base.docs.description == desc

@pytest.mark.parametrize("desc", (["A short description"], 123))
@pytest.mark.skip
def test_invalid_description_type(self, desc: Any) -> None:
bt = {"name": self.descriptor_name, "description": desc}
with pytest.raises(TypeError):
from_boutiques(bt, self.package_name)

# NOTE: Can only pass a single string of author(s) via boutiques
def test_valid_authors(self) -> None:
authors = "Author One"
bt = {"name": self.descriptor_name, "author": authors}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.base.docs.authors, list)
assert out.command.base.docs.authors == [authors]

@pytest.mark.parametrize("authors", (["Author One"], 123))
@pytest.mark.skip
def test_invalid_author_type(self, authors: Any) -> None:
bt = {"name": self.descriptor_name, "author": authors}

with pytest.raises(TypeError):
from_boutiques(bt, self.package_name)

# NOTE: Can only pass a single string of url(s) via boutiques
def test_valid_urls(self) -> None:
urls = "https://url.com"
bt = {"name": self.descriptor_name, "url": urls}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.base.docs.urls, list)
assert out.command.base.docs.urls == [urls]

@pytest.mark.parametrize("urls", (["https://url.com"], 123))
@pytest.mark.skip
def test_invalid_urls_type(self, urls: Any) -> None:
bt = {"name": self.descriptor_name, "url": urls}

with pytest.raises(TypeError):
from_boutiques(bt, self.package_name)
127 changes: 127 additions & 0 deletions tests/frontend/boutiques/test_inputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import styx.ir.core as ir
from styx.frontend.boutiques.core import from_boutiques


class TestPrimitiveParams:
"""Test primitive param types.

Main difference in primitive types between boutiques and IR are:

- IR has separate int and float types, boutiques just "Number"
- Boutiques "Flag" type maps to IR bool which is encoded differently.
"""

package_name = "My package"
descriptor_name = "My descriptor"

def test_int(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": "dummy [X]",
"inputs": [
{
"id": "x",
"value-key": "[X]",
"type": "Number",
"integer": True,
}
],
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
param = groups[1].cargs[0].tokens[0]
assert isinstance(param, ir.Param)
assert isinstance(param.body, ir.Param.Int)

def test_float(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": "dummy [X]",
"inputs": [
{
"id": "x",
"value-key": "[X]",
"type": "Number",
# "integer": False, # Default is float
}
],
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
param = groups[1].cargs[0].tokens[0]
assert isinstance(param, ir.Param)
assert isinstance(param.body, ir.Param.Float)

def test_file(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": "dummy [X]",
"inputs": [
{
"id": "x",
"value-key": "[X]",
"type": "File",
}
],
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
param = groups[1].cargs[0].tokens[0]
assert isinstance(param, ir.Param)
assert isinstance(param.body, ir.Param.File)

def test_string(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": "dummy [X]",
"inputs": [
{
"id": "x",
"value-key": "[X]",
"type": "String",
}
],
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
param = groups[1].cargs[0].tokens[0]
assert isinstance(param, ir.Param)
assert isinstance(param.body, ir.Param.String)

def test_bool(self) -> None:
bt = {
"name": self.descriptor_name,
"command-line": "dummy [X]",
"inputs": [
{
"id": "x",
"value-key": "[X]",
"type": "Flag",
"command-line-flag": "--x",
}
],
}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.command.body, ir.Param.Struct)
groups = out.command.body.groups
assert len(groups) == 2
param = groups[1].cargs[0].tokens[0]
assert isinstance(param, ir.Param)
assert isinstance(param.body, ir.Param.Bool)
assert param.body.value_true == ["--x"]
assert param.body.value_false == []
assert not param.nullable
assert param.default_value is False # check identity to ensure it's False and not None
44 changes: 44 additions & 0 deletions tests/frontend/boutiques/test_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import Any

import pytest

import styx.ir.core as ir
from styx.frontend.boutiques.core import from_boutiques


class TestMetadata:
package_name = "My package"
descriptor_name = "My descriptor"

@pytest.mark.parametrize("version", ("0.0.0", None))
def test_valid_version(self, version: str | None) -> None:
bt = {"name": self.descriptor_name, "tool-version": version}
out = from_boutiques(bt, self.package_name)

assert isinstance(out.package, ir.Package)
assert out.package.name == self.package_name
assert out.package.version == version

@pytest.mark.parametrize("version", (1.23, ["version"]))
@pytest.mark.skip
def test_invalid_version_type(self, version: Any) -> None:
bt = {"name": self.descriptor_name, "tool-version": version}

with pytest.raises(TypeError):
from_boutiques(bt, self.package_name)

@pytest.mark.parametrize("image", ("container:version", None))
def test_valid_docker(self, image: str | None) -> None:
bt = {"name": self.descriptor_name, "container-image": {"image": image}}
out = from_boutiques(bt, self.package_name)
assert isinstance(out.package, ir.Package)
assert out.package.name == self.package_name
assert out.package.docker == image

@pytest.mark.parametrize("image", (123, ["list of str"]))
@pytest.mark.skip
def test_invalid_docker_type(self, image: Any) -> None:
bt = {"name": self.descriptor_name, "container-image": {"image": image}}

with pytest.raises(TypeError):
from_boutiques(bt, self.package_name)
Empty file.
1 change: 1 addition & 0 deletions tests/legacy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Legacy tests."""
Loading
Loading