Source code for cgsmiles.dialects
from inspect import signature, Signature, Parameter
from functools import partial
[docs]
def check_and_cast_types(bound_args, signature):
for name, value in bound_args.arguments.items():
param = signature.parameters.get(name)
# Check if a type annotation is present
if param and param.annotation != Parameter.empty:
expected_type = param.annotation
# Attempt type casting if the value is not of the expected type
if not isinstance(value, expected_type):
try:
bound_args.arguments[name] = expected_type(value)
except (TypeError, ValueError):
raise TypeError(f"Argument '{name}' must be of type {expected_type.__name__}")
return bound_args
def _parse_dialect_string(string_iterable,
dialect_signature,
arg_to_fullname={},
annotation_sep_token=';',
annotation_assign_token='=',
drop_none=True):
"""
This base function parsers a string that describes key value pairs
in having a pattern of:
key<annotation_assign_token>value<annotation_sep_token>key ...
Default values, non-keyword agruments and types are defined using the
dialect signature object. If args are defined the key and assignment
token may be omitted.
Neither the `annotation_sep_token` nor the `annotation_assign_token`
can be part of key or value. A SyntaxError is raised in this case.
Parameters
----------
string_iterable: :type data: iter
the string or iter object that contains the string
dialect_signature: inspect.Signature
a signature defineing args, kwargs, default values
and types
arg_to_fullname: dict
maps arguments to more verbose descriptions
annotation_sep_token: str
character used to seperate key value pairs
annotation_assign_token: str
character used to assign a key from a value
drop_none: bool
drop all entries with value equal to None
Returns
-------
dict
dict of key value paris
Raises
------
SyntaxError
an error is raised if the signature does not match or
too many annotation_assign_token are given
"""
args_found = []
kwargs_found = {}
if len(string_iterable) > 0:
elements = string_iterable.split(annotation_sep_token)
for entry in elements:
if entry.count('=') > 1:
# this takes care of too many '=' chacaters
msg = (f"Your annotation {entry} contains too many "
f"{annotation_assign_token} charachters. Only"
"chacracter per key value pair is allowed")
raise SyntaxError(msg)
key_value = entry.split(annotation_assign_token)
if len(key_value) == 1:
args_found.append(key_value[0])
else:
kwargs_found[key_value[0]] = key_value[1]
try:
applied_labels = dialect_signature.bind(*args_found,
**kwargs_found)
except TypeError as emsg:
print(emsg)
msg = ("You have too many positional arguments or "
f"{annotation_sep_token} as part of key value "
"pairs which is not allowed.")
raise SyntaxError(msg)
applied_labels = check_and_cast_types(applied_labels,
dialect_signature)
applied_labels.apply_defaults()
# drop all attributes that are None by default
not_defined = [ attr for attr, value in applied_labels.arguments.items() if value is None]
for attr in not_defined:
del applied_labels.arguments[attr]
# convert keys to more verbose names
# this should only apply to args know to
# the signature
for old_key, new_key in arg_to_fullname.items():
if old_key in applied_labels.arguments:
applied_labels.arguments[new_key] = applied_labels.arguments.pop(old_key)
# if there are kwargs we need to put them into
# output dict
out_args = {}
if 'kwargs' in applied_labels.arguments:
out_args.update(applied_labels.arguments['kwargs'])
del applied_labels.arguments['kwargs']
out_args.update(applied_labels.arguments)
return out_args
[docs]
def create_dialect(default_attributes,
optional_attributes={},
accept_kwargs=True):
"""
Creates a signature of default annotations.
Note that the order of the entries in the dict
determines the order of the args accepted.
"""
parameters = []
for argname, (default_value, arg_type) in default_attributes.items():
parameters.append(Parameter(argname,
Parameter.POSITIONAL_OR_KEYWORD,
default=default_value,
annotation=arg_type))
if accept_kwargs:
parameters.append(Parameter('kwargs',
kind=Parameter.VAR_KEYWORD))
sig = Signature(parameters)
return sig
##########################################################
# KNOWN DIALECTS #
##########################################################
# this one is for global use
# it is the base CGsmiles dialect
CGSMILES_DEFAULT_DIALECT = create_dialect({"fragname": (None, str),
"q": (0.0, float),
"w": (1.0, float)})
parse_graph_base_node = partial(_parse_dialect_string,
dialect_signature=CGSMILES_DEFAULT_DIALECT,
arg_to_fullname = {"w": "weight", "q": "charge"})
# this one is an internal fukery until the pysmiles
# base parser is available
# it just strips the kwargs from fragments before
# they go to the respective parser
# in case of cgsmiles fragments it is a bit doing
# double the work
fragment_base = create_dialect({"w": (1.0, float), "x": (None, str)}, accept_kwargs=True)
_fragment_node_parser = partial(_parse_dialect_string,
dialect_signature=fragment_base,
arg_to_fullname = {"w": "weight", "x": "chiral"})