Skip to content

Commit

Permalink
Merge pull request #289 from dmgav/extend-type-comprehension
Browse files Browse the repository at this point in the history
Small improvements of plan parameter validation
  • Loading branch information
dmgav authored Aug 18, 2023
2 parents af6015b + d5140ff commit f7d489f
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 14 deletions.
52 changes: 38 additions & 14 deletions bluesky_queueserver/manager/profile_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
import tempfile
import traceback
import typing
from collections.abc import Iterable
from collections.abc import Iterable, Mapping

import jsonschema
import pydantic
import yaml
from numpydoc.docscrape import NumpyDocString
from packaging import version

import bluesky_queueserver

Expand All @@ -30,6 +31,8 @@
logger = logging.getLogger(__name__)
qserver_version = bluesky_queueserver.__version__

pydantic_version_major = version.parse(pydantic.__version__).major


class ScriptLoadingError(Exception):
def __init__(self, msg, tb):
Expand Down Expand Up @@ -1932,17 +1935,25 @@ def _compare_types(object_in, object_out):
then the function is recursively called for each element. A set of rules is
applied to types to determine if the types are matching.
"""
if isinstance(object_in, (list, tuple)) and isinstance(object_out, (list, tuple)):

def both_iterables(x, y):
return isinstance(x, Iterable) and isinstance(y, Iterable) and not isinstance(x, str)

def both_mappings(x, y):
return isinstance(x, Mapping) and isinstance(y, Mapping)

if both_iterables(object_in, object_out):
match = True
if len(object_in) == len(object_out):
for o_in, o_out in zip(object_in, object_out):
if not _compare_types(o_in, o_out):
match = False
else:
# This should never happen, since 'object_out' is obtained by
# applying transformations on 'object_in', but it is still
# good to check to avoid exceptions.
match = False
for o_in, o_out in zip(object_in, object_out):
if not _compare_types(o_in, o_out):
match = False
elif both_mappings(object_in, object_out):
match = True
for k in object_in.keys():
if k not in object_out:
match = False
if not _compare_types(object_in[k], object_out[k]):
match = False
else:
match = False
# This is the set of rules used to determine if types are matching.
Expand Down Expand Up @@ -2142,10 +2153,16 @@ def _process_annotation(encoded_annotation, *, ns=None):
ns: dict
Namespace dictionary with created types.
"""
import bluesky

# Namespace that contains the types created in the process of processing the annotation
ns = ns or {}
if "typing" not in ns:
ns.update({"typing": typing})
if "collections" not in ns:
ns.update({"collections": collections})
if "bluesky" not in ns:
ns.update({"bluesky": bluesky})
if "enum" not in ns:
ns.update({"enum": enum})
if "NoneType" not in ns:
Expand Down Expand Up @@ -2452,8 +2469,8 @@ def _validate_plan_parameters(param_list, call_args, call_kwargs):
# recommended 'm.dict()', because 'm.dict()' was causing performance problems
# when validating large batches of plans. Based on testing, 'm.__dict__' seems
# to work fine in this application.
# NOTE: the following step may not be needed once Pydantic 1 is deprecated,
# because Pydantic 2 performs strict type checking.
# NOTE: Pydantic 2 performs strict type checking and some checks performed by
# '_compare_in_out' may not be needed.
success, msg = _compare_in_out(bound_args.arguments, m.__dict__)
if not success:
raise ValueError(f"Error in argument types: {msg}")
Expand Down Expand Up @@ -2900,6 +2917,7 @@ def convert_annotation_to_string(annotation):
"""
Ignore the type if it can not be properly reconstructed using 'eval'
"""
import bluesky
from bluesky.protocols import Flyable, Movable, Readable

# Patterns for callables
Expand All @@ -2909,7 +2927,13 @@ def convert_annotation_to_string(annotation):
protocols_mapping = {"__READABLE__": Readable, "__MOVABLE__": Movable, "__FLYABLE__": Flyable}
protocols_inv = {v: k for k, v in protocols_mapping.items()}

ns = {"typing": typing, "collections": collections, "NoneType": type(None), **protocols_mapping}
ns = {
"typing": typing,
"collections": collections,
"bluesky": bluesky,
"NoneType": type(None),
**protocols_mapping,
}

# This will work for generic types like 'typing.List[int]'
a_str = f"{annotation!r}"
Expand Down
Empty file added pprint
Empty file.

0 comments on commit f7d489f

Please sign in to comment.