Skip to content

Commit

Permalink
feat: changes visibility of classes and methods in schemav1 (#99)
Browse files Browse the repository at this point in the history
* refactor: changes visilibity of classes and methods in schemav1

* feat: update rules for object names

* style: fix black issues

* style: fixed flake8 settings
  • Loading branch information
mstechly authored Jun 25, 2024
1 parent 966f67a commit 2f2c4eb
Show file tree
Hide file tree
Showing 10 changed files with 66 additions and 75 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[flake8]
max-line-length: 100
max-line-length: 120
docstring-convention = google
ignore =
# Allow continuation line under-indented for visual indent.
Expand Down
5 changes: 2 additions & 3 deletions docs/library/reference/qref.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
::: qref
handler: python
options:
members:
- generate_program_schema
- SchemaV1
filters:
- "!__all__"
6 changes: 6 additions & 0 deletions docs/library/reference/qref.schema_v1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
::: qref.schema_v1
handler: python
options:
filters:
- "!^_[^_]"
- "!SchemaV1"
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ nav:
- library/userguide.md
- API Reference:
- qref: library/reference/qref.md
- qref.schema_v1: library/reference/qref.schema_v1.md
- qref.experimental.rendering: library/reference/qref.experimental.rendering.md
- development.md
theme:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ build-backend = "poetry_dynamic_versioning.backend"


[tool.black]
line-length = 100
line-length = 120
target-version = ['py39']


Expand Down
5 changes: 3 additions & 2 deletions src/qref/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

from typing import Any

from ._schema_v1 import SchemaV1, generate_schema_v1
from .schema_v1 import SchemaV1, generate_schema_v1
from .verification import verify_topology

SCHEMA_GENERATORS = {"v1": generate_schema_v1}
MODELS = {"v1": SchemaV1}
Expand All @@ -41,4 +42,4 @@ def generate_program_schema(version: str = LATEST_SCHEMA_VERSION) -> dict[str, A
raise ValueError(f"Unknown schema version {version}")


__all__ = ["generate_program_schema", "SchemaV1"]
__all__ = ["generate_program_schema", "SchemaV1", "verify_topology"]
12 changes: 3 additions & 9 deletions src/qref/experimental/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,7 @@ def _format_node_name(node_name, parent):


def _add_nonleaf_ports(ports, parent_cluster, parent_path: str, group_name):
with parent_cluster.subgraph(
name=f"{parent_path}: {group_name}", graph_attr=PORT_GROUP_ATTRS
) as subgraph:
with parent_cluster.subgraph(name=f"{parent_path}: {group_name}", graph_attr=PORT_GROUP_ATTRS) as subgraph:
for port in ports:
subgraph.node(name=f"{parent_path}.{port.name}", label=port.name, **PORT_NODE_KWARGS)

Expand All @@ -114,9 +112,7 @@ def _add_nonleaf(routine, dag: graphviz.Digraph, parent_path: str) -> None:
input_ports, output_ports = _split_ports(routine.ports)
full_path = f"{parent_path}.{routine.name}"

with dag.subgraph(
name=f"cluster_{full_path}", graph_attr={"label": routine.name, **CLUSTER_KWARGS}
) as cluster:
with dag.subgraph(name=f"cluster_{full_path}", graph_attr={"label": routine.name, **CLUSTER_KWARGS}) as cluster:
_add_nonleaf_ports(input_ports, cluster, full_path, "inputs")
_add_nonleaf_ports(output_ports, cluster, full_path, "outputs")

Expand Down Expand Up @@ -161,9 +157,7 @@ def to_graphviz(data: Union[dict, SchemaV1]) -> graphviz.Digraph:

def render_entry_point():
parser = ArgumentParser()
parser.add_argument(
"input", help="Path to the YAML or JSON file with Routine in V1 schema", type=Path
)
parser.add_argument("input", help="Path to the YAML or JSON file with Routine in V1 schema", type=Path)
parser.add_argument(
"output",
help=(
Expand Down
70 changes: 40 additions & 30 deletions src/qref/_schema_v1.py → src/qref/schema_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,52 +29,64 @@
from pydantic.json_schema import GenerateJsonSchema

NAME_PATTERN = "[A-Za-z_][A-Za-z0-9_]*"
NAMESPACED_NAME_PATTERN = rf"{NAME_PATTERN}\.{NAME_PATTERN}"

Name = Annotated[str, StringConstraints(pattern=rf"^{NAME_PATTERN}$")]
NamespacedName = Annotated[str, StringConstraints(pattern=rf"^{NAMESPACED_NAME_PATTERN}")]
OptionallyNamespacedName = Annotated[
str, StringConstraints(pattern=rf"^(({NAME_PATTERN})|({NAMESPACED_NAME_PATTERN}))$")
OPTIONALLY_NAMESPACED_NAME_PATTERN = rf"^({NAME_PATTERN}\.)?{NAME_PATTERN}$"
MULTINAMESPACED_NAME_PATTERN = rf"^({NAME_PATTERN}\.)+{NAME_PATTERN}$"
OPTIONALLY_MULTINAMESPACED_NAME_PATTERN = rf"^({NAME_PATTERN}\.)*{NAME_PATTERN}$"

_Name = Annotated[str, StringConstraints(pattern=rf"^{NAME_PATTERN}$")]
_OptionallyNamespacedName = Annotated[str, StringConstraints(pattern=rf"{OPTIONALLY_NAMESPACED_NAME_PATTERN}")]
_MultiNamespacedName = Annotated[str, StringConstraints(pattern=rf"{MULTINAMESPACED_NAME_PATTERN}")]
_OptionallyMultiNamespacedName = Annotated[
str, StringConstraints(pattern=rf"{OPTIONALLY_MULTINAMESPACED_NAME_PATTERN}")
]

_Value = Union[int, float, str]


def sorter(key):
def _sorter(key):
def _inner(v):
return sorted(v, key=key)

return _inner


name_sorter = AfterValidator(sorter(lambda p: p.name))
source_sorter = AfterValidator(sorter(lambda c: c.source))
_name_sorter = AfterValidator(_sorter(lambda p: p.name))
_source_sorter = AfterValidator(_sorter(lambda c: c.source))


class PortV1(BaseModel):
"""Description of Port in V1 schema"""

class _PortV1(BaseModel):
name: Name
name: _Name
direction: Literal["input", "output", "through"]
size: Optional[_Value]
model_config = ConfigDict(title="Port")


class _ConnectionV1(BaseModel):
source: OptionallyNamespacedName
target: OptionallyNamespacedName
class ConnectionV1(BaseModel):
"""Description of Connection in V1 schema"""

source: _OptionallyNamespacedName
target: _OptionallyNamespacedName

model_config = ConfigDict(title="Connection", use_enum_values=True)


class _ResourceV1(BaseModel):
name: Name
class ResourceV1(BaseModel):
"""Description of Resource in V1 schema"""

name: _Name
type: Literal["additive", "multiplicative", "qubits", "other"]
value: Union[int, float, str, None]

model_config = ConfigDict(title="Resource")


class _ParamLinkV1(BaseModel):
source: Name
targets: list[NamespacedName]
class ParamLinkV1(BaseModel):
"""Description of Parameter link in V1 schema"""

source: _OptionallyNamespacedName
targets: list[_MultiNamespacedName]

model_config = ConfigDict(title="ParamLink")

Expand All @@ -87,15 +99,15 @@ class RoutineV1(BaseModel):
SchemaV1.
"""

name: Name
children: Annotated[list[RoutineV1], name_sorter] = Field(default_factory=list)
name: _Name
children: Annotated[list[RoutineV1], _name_sorter] = Field(default_factory=list)
type: Optional[str] = None
ports: Annotated[list[_PortV1], name_sorter] = Field(default_factory=list)
resources: Annotated[list[_ResourceV1], name_sorter] = Field(default_factory=list)
connections: Annotated[list[_ConnectionV1], source_sorter] = Field(default_factory=list)
input_params: list[Name] = Field(default_factory=list)
ports: Annotated[list[PortV1], _name_sorter] = Field(default_factory=list)
resources: Annotated[list[ResourceV1], _name_sorter] = Field(default_factory=list)
connections: Annotated[list[ConnectionV1], _source_sorter] = Field(default_factory=list)
input_params: list[_OptionallyMultiNamespacedName] = Field(default_factory=list)
local_variables: list[str] = Field(default_factory=list)
linked_params: Annotated[list[_ParamLinkV1], source_sorter] = Field(default_factory=list)
linked_params: Annotated[list[ParamLinkV1], _source_sorter] = Field(default_factory=list)
meta: dict[str, Any] = Field(default_factory=dict)

model_config = ConfigDict(title="Routine")
Expand All @@ -105,11 +117,9 @@ def __init__(self, **data: Any):

@field_validator("connections", mode="after")
@classmethod
def _validate_connections(cls, v, values) -> list[_ConnectionV1]:
def _validate_connections(cls, v, values) -> list[ConnectionV1]:
children_port_names = [
f"{child.name}.{port.name}"
for child in values.data.get("children")
for port in child.ports
f"{child.name}.{port.name}" for child in values.data.get("children") for port in child.ports
]
parent_port_names = [port.name for port in values.data["ports"]]
available_port_names = set(children_port_names + parent_port_names)
Expand Down
16 changes: 6 additions & 10 deletions src/qref/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from dataclasses import dataclass
from typing import Optional, Union

from ._schema_v1 import RoutineV1, SchemaV1
from .schema_v1 import RoutineV1, SchemaV1


@dataclass
Expand All @@ -36,6 +36,8 @@ def __bool__(self) -> bool:
def verify_topology(routine: Union[SchemaV1, RoutineV1]) -> TopologyVerificationOutput:
"""Checks whether program has correct topology.
Correct topology cannot include cycles or disconnected ports.
Args:
routine: Routine or program to be verified.
"""
Expand All @@ -58,9 +60,7 @@ def _verify_routine_topology(routine: RoutineV1) -> list[str]:
return problems


def _get_adjacency_list_from_routine(
routine: RoutineV1, path: Optional[str]
) -> dict[str, list[str]]:
def _get_adjacency_list_from_routine(routine: RoutineV1, path: Optional[str]) -> dict[str, list[str]]:
"""This function creates a flat graph representing one hierarchy level of a routine.
Nodes represent ports and edges represent connections (they're directed).
Expand Down Expand Up @@ -135,17 +135,13 @@ def _find_disconnected_ports(routine: RoutineV1):
for port in child.ports:
pname = f"{routine.name}.{child.name}.{port.name}"
if port.direction == "input":
matches_in = [
c for c in routine.connections if c.target == f"{child.name}.{port.name}"
]
matches_in = [c for c in routine.connections if c.target == f"{child.name}.{port.name}"]
if len(matches_in) == 0:
problems.append(f"No incoming connections to {pname}.")
elif len(matches_in) > 1:
problems.append(f"Too many incoming connections to {pname}.")
elif port.direction == "output":
matches_out = [
c for c in routine.connections if c.source == f"{child.name}.{port.name}"
]
matches_out = [c for c in routine.connections if c.source == f"{child.name}.{port.name}"]
if len(matches_out) == 0:
problems.append(f"No outgoing connections from {pname}.")
elif len(matches_out) > 1:
Expand Down
22 changes: 3 additions & 19 deletions tests/qref/data/invalid_yaml_programs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@
target: bar.in_0
description: "Connections have more than one namespace"
error_path: "$.program.connections[0].source"
error_message: "'foo.foo.out_0' does not match '^(([A-Za-z_][A-Za-z0-9_]*)|([A-Za-z_][A-Za-z0-9_]*\\\\.[A-Za-z_][A-Za-z0-9_]*))$'"
error_message: "'foo.foo.out_0' does not match '^([A-Za-z_][A-Za-z0-9_]*\\\\.)?[A-Za-z_][A-Za-z0-9_]*$'"
- input:
version: v1
program:
Expand All @@ -204,30 +204,14 @@
- "my-input-param"
description: "Input param has invalid name"
error_path: "$.program.input_params[1]"
error_message: "'my-input-param' does not match '^[A-Za-z_][A-Za-z0-9_]*$'"
error_message: "'my-input-param' does not match '^([A-Za-z_][A-Za-z0-9_]*\\\\.)*[A-Za-z_][A-Za-z0-9_]*$'"
- input:
version: v1
program:
name: ""
description: "Program has an empty name"
error_path: "$.program.name"
error_message: "'' does not match '^[A-Za-z_][A-Za-z0-9_]*$'"
- input:
version: v1
program:
name: "root"
input_params:
- N
linked_params:
- source: foo.N
targets: [foo.N]
children:
- name: foo
input_params:
- N
description: Source of a paramater link is namespaced
error_path: "$.program.linked_params[0].source"
error_message: "'foo.N' does not match '^[A-Za-z_][A-Za-z0-9_]*$'"
- input:
version: v1
program:
Expand All @@ -243,5 +227,5 @@
- N
description: "Target of a paramater link is not namespaced"
error_path: "$.program.linked_params[0].targets[0]"
error_message: "'N' does not match '^[A-Za-z_][A-Za-z0-9_]*\\\\.[A-Za-z_][A-Za-z0-9_]*'"
error_message: "'N' does not match '^([A-Za-z_][A-Za-z0-9_]*\\\\.)+[A-Za-z_][A-Za-z0-9_]*$'"

0 comments on commit 2f2c4eb

Please sign in to comment.