| | """Options manager for :class:`~.Poly` and public API functions. """ |
| |
|
| | from __future__ import annotations |
| |
|
| | __all__ = ["Options"] |
| |
|
| | from sympy.core.basic import Basic |
| | from sympy.core.expr import Expr |
| | from sympy.core.sympify import sympify |
| | from sympy.polys.polyerrors import GeneratorsError, OptionError, FlagError |
| | from sympy.utilities import numbered_symbols, topological_sort, public |
| | from sympy.utilities.iterables import has_dups, is_sequence |
| |
|
| | import sympy.polys |
| |
|
| | import re |
| |
|
| | class Option: |
| | """Base class for all kinds of options. """ |
| |
|
| | option: str | None = None |
| |
|
| | is_Flag = False |
| |
|
| | requires: list[str] = [] |
| | excludes: list[str] = [] |
| |
|
| | after: list[str] = [] |
| | before: list[str] = [] |
| |
|
| | @classmethod |
| | def default(cls): |
| | return None |
| |
|
| | @classmethod |
| | def preprocess(cls, option): |
| | return None |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | pass |
| |
|
| |
|
| | class Flag(Option): |
| | """Base class for all kinds of flags. """ |
| |
|
| | is_Flag = True |
| |
|
| |
|
| | class BooleanOption(Option): |
| | """An option that must have a boolean value or equivalent assigned. """ |
| |
|
| | @classmethod |
| | def preprocess(cls, value): |
| | if value in [True, False]: |
| | return bool(value) |
| | else: |
| | raise OptionError("'%s' must have a boolean value assigned, got %s" % (cls.option, value)) |
| |
|
| |
|
| | class OptionType(type): |
| | """Base type for all options that does registers options. """ |
| |
|
| | def __init__(cls, *args, **kwargs): |
| | @property |
| | def getter(self): |
| | try: |
| | return self[cls.option] |
| | except KeyError: |
| | return cls.default() |
| |
|
| | setattr(Options, cls.option, getter) |
| | Options.__options__[cls.option] = cls |
| |
|
| |
|
| | @public |
| | class Options(dict): |
| | """ |
| | Options manager for polynomial manipulation module. |
| | |
| | Examples |
| | ======== |
| | |
| | >>> from sympy.polys.polyoptions import Options |
| | >>> from sympy.polys.polyoptions import build_options |
| | |
| | >>> from sympy.abc import x, y, z |
| | |
| | >>> Options((x, y, z), {'domain': 'ZZ'}) |
| | {'auto': False, 'domain': ZZ, 'gens': (x, y, z)} |
| | |
| | >>> build_options((x, y, z), {'domain': 'ZZ'}) |
| | {'auto': False, 'domain': ZZ, 'gens': (x, y, z)} |
| | |
| | **Options** |
| | |
| | * Expand --- boolean option |
| | * Gens --- option |
| | * Wrt --- option |
| | * Sort --- option |
| | * Order --- option |
| | * Field --- boolean option |
| | * Greedy --- boolean option |
| | * Domain --- option |
| | * Split --- boolean option |
| | * Gaussian --- boolean option |
| | * Extension --- option |
| | * Modulus --- option |
| | * Symmetric --- boolean option |
| | * Strict --- boolean option |
| | |
| | **Flags** |
| | |
| | * Auto --- boolean flag |
| | * Frac --- boolean flag |
| | * Formal --- boolean flag |
| | * Polys --- boolean flag |
| | * Include --- boolean flag |
| | * All --- boolean flag |
| | * Gen --- flag |
| | * Series --- boolean flag |
| | |
| | """ |
| |
|
| | __order__ = None |
| | __options__: dict[str, type[Option]] = {} |
| |
|
| | gens: tuple[Expr, ...] |
| | domain: sympy.polys.domains.Domain |
| |
|
| | def __init__(self, gens, args, flags=None, strict=False): |
| | dict.__init__(self) |
| |
|
| | if gens and args.get('gens', ()): |
| | raise OptionError( |
| | "both '*gens' and keyword argument 'gens' supplied") |
| | elif gens: |
| | args = dict(args) |
| | args['gens'] = gens |
| |
|
| | defaults = args.pop('defaults', {}) |
| |
|
| | def preprocess_options(args): |
| | for option, value in args.items(): |
| | try: |
| | cls = self.__options__[option] |
| | except KeyError: |
| | raise OptionError("'%s' is not a valid option" % option) |
| |
|
| | if issubclass(cls, Flag): |
| | if flags is None or option not in flags: |
| | if strict: |
| | raise OptionError("'%s' flag is not allowed in this context" % option) |
| |
|
| | if value is not None: |
| | self[option] = cls.preprocess(value) |
| |
|
| | preprocess_options(args) |
| |
|
| | for key in dict(defaults): |
| | if key in self: |
| | del defaults[key] |
| | else: |
| | for option in self.keys(): |
| | cls = self.__options__[option] |
| |
|
| | if key in cls.excludes: |
| | del defaults[key] |
| | break |
| |
|
| | preprocess_options(defaults) |
| |
|
| | for option in self.keys(): |
| | cls = self.__options__[option] |
| |
|
| | for require_option in cls.requires: |
| | if self.get(require_option) is None: |
| | raise OptionError("'%s' option is only allowed together with '%s'" % (option, require_option)) |
| |
|
| | for exclude_option in cls.excludes: |
| | if self.get(exclude_option) is not None: |
| | raise OptionError("'%s' option is not allowed together with '%s'" % (option, exclude_option)) |
| |
|
| | for option in self.__order__: |
| | self.__options__[option].postprocess(self) |
| |
|
| | @classmethod |
| | def _init_dependencies_order(cls): |
| | """Resolve the order of options' processing. """ |
| | if cls.__order__ is None: |
| | vertices, edges = [], set() |
| |
|
| | for name, option in cls.__options__.items(): |
| | vertices.append(name) |
| |
|
| | edges.update((_name, name) for _name in option.after) |
| |
|
| | edges.update((name, _name) for _name in option.before) |
| |
|
| | try: |
| | cls.__order__ = topological_sort((vertices, list(edges))) |
| | except ValueError: |
| | raise RuntimeError( |
| | "cycle detected in sympy.polys options framework") |
| |
|
| | def clone(self, updates={}): |
| | """Clone ``self`` and update specified options. """ |
| | obj = dict.__new__(self.__class__) |
| |
|
| | for option, value in self.items(): |
| | obj[option] = value |
| |
|
| | for option, value in updates.items(): |
| | obj[option] = value |
| |
|
| | return obj |
| |
|
| | def __setattr__(self, attr, value): |
| | if attr in self.__options__: |
| | self[attr] = value |
| | else: |
| | super().__setattr__(attr, value) |
| |
|
| | @property |
| | def args(self): |
| | args = {} |
| |
|
| | for option, value in self.items(): |
| | if value is not None and option != 'gens': |
| | cls = self.__options__[option] |
| |
|
| | if not issubclass(cls, Flag): |
| | args[option] = value |
| |
|
| | return args |
| |
|
| | @property |
| | def options(self): |
| | options = {} |
| |
|
| | for option, cls in self.__options__.items(): |
| | if not issubclass(cls, Flag): |
| | options[option] = getattr(self, option) |
| |
|
| | return options |
| |
|
| | @property |
| | def flags(self): |
| | flags = {} |
| |
|
| | for option, cls in self.__options__.items(): |
| | if issubclass(cls, Flag): |
| | flags[option] = getattr(self, option) |
| |
|
| | return flags |
| |
|
| |
|
| | class Expand(BooleanOption, metaclass=OptionType): |
| | """``expand`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'expand' |
| |
|
| | requires: list[str] = [] |
| | excludes: list[str] = [] |
| |
|
| | @classmethod |
| | def default(cls): |
| | return True |
| |
|
| |
|
| | class Gens(Option, metaclass=OptionType): |
| | """``gens`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'gens' |
| |
|
| | requires: list[str] = [] |
| | excludes: list[str] = [] |
| |
|
| | @classmethod |
| | def default(cls): |
| | return () |
| |
|
| | @classmethod |
| | def preprocess(cls, gens): |
| | if isinstance(gens, Basic): |
| | gens = (gens,) |
| | elif len(gens) == 1 and is_sequence(gens[0]): |
| | gens = gens[0] |
| |
|
| | if gens == (None,): |
| | gens = () |
| | elif has_dups(gens): |
| | raise GeneratorsError("duplicated generators: %s" % str(gens)) |
| | elif any(gen.is_commutative is False for gen in gens): |
| | raise GeneratorsError("non-commutative generators: %s" % str(gens)) |
| |
|
| | return tuple(gens) |
| |
|
| |
|
| | class Wrt(Option, metaclass=OptionType): |
| | """``wrt`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'wrt' |
| |
|
| | requires: list[str] = [] |
| | excludes: list[str] = [] |
| |
|
| | _re_split = re.compile(r"\s*,\s*|\s+") |
| |
|
| | @classmethod |
| | def preprocess(cls, wrt): |
| | if isinstance(wrt, Basic): |
| | return [str(wrt)] |
| | elif isinstance(wrt, str): |
| | wrt = wrt.strip() |
| | if wrt.endswith(','): |
| | raise OptionError('Bad input: missing parameter.') |
| | if not wrt: |
| | return [] |
| | return list(cls._re_split.split(wrt)) |
| | elif hasattr(wrt, '__getitem__'): |
| | return list(map(str, wrt)) |
| | else: |
| | raise OptionError("invalid argument for 'wrt' option") |
| |
|
| |
|
| | class Sort(Option, metaclass=OptionType): |
| | """``sort`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'sort' |
| |
|
| | requires: list[str] = [] |
| | excludes: list[str] = [] |
| |
|
| | @classmethod |
| | def default(cls): |
| | return [] |
| |
|
| | @classmethod |
| | def preprocess(cls, sort): |
| | if isinstance(sort, str): |
| | return [ gen.strip() for gen in sort.split('>') ] |
| | elif hasattr(sort, '__getitem__'): |
| | return list(map(str, sort)) |
| | else: |
| | raise OptionError("invalid argument for 'sort' option") |
| |
|
| |
|
| | class Order(Option, metaclass=OptionType): |
| | """``order`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'order' |
| |
|
| | requires: list[str] = [] |
| | excludes: list[str] = [] |
| |
|
| | @classmethod |
| | def default(cls): |
| | return sympy.polys.orderings.lex |
| |
|
| | @classmethod |
| | def preprocess(cls, order): |
| | return sympy.polys.orderings.monomial_key(order) |
| |
|
| |
|
| | class Field(BooleanOption, metaclass=OptionType): |
| | """``field`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'field' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['domain', 'split', 'gaussian'] |
| |
|
| |
|
| | class Greedy(BooleanOption, metaclass=OptionType): |
| | """``greedy`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'greedy' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric'] |
| |
|
| |
|
| | class Composite(BooleanOption, metaclass=OptionType): |
| | """``composite`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'composite' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return None |
| |
|
| | requires: list[str] = [] |
| | excludes = ['domain', 'split', 'gaussian', 'extension', 'modulus', 'symmetric'] |
| |
|
| |
|
| | class Domain(Option, metaclass=OptionType): |
| | """``domain`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'domain' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['field', 'greedy', 'split', 'gaussian', 'extension'] |
| |
|
| | after = ['gens'] |
| |
|
| | _re_realfield = re.compile(r"^(R|RR)(_(\d+))?$") |
| | _re_complexfield = re.compile(r"^(C|CC)(_(\d+))?$") |
| | _re_finitefield = re.compile(r"^(FF|GF)\((\d+)\)$") |
| | _re_polynomial = re.compile(r"^(Z|ZZ|Q|QQ|ZZ_I|QQ_I|R|RR|C|CC)\[(.+)\]$") |
| | _re_fraction = re.compile(r"^(Z|ZZ|Q|QQ)\((.+)\)$") |
| | _re_algebraic = re.compile(r"^(Q|QQ)\<(.+)\>$") |
| |
|
| | @classmethod |
| | def preprocess(cls, domain): |
| | if isinstance(domain, sympy.polys.domains.Domain): |
| | return domain |
| | elif hasattr(domain, 'to_domain'): |
| | return domain.to_domain() |
| | elif isinstance(domain, str): |
| | if domain in ['Z', 'ZZ']: |
| | return sympy.polys.domains.ZZ |
| |
|
| | if domain in ['Q', 'QQ']: |
| | return sympy.polys.domains.QQ |
| |
|
| | if domain == 'ZZ_I': |
| | return sympy.polys.domains.ZZ_I |
| |
|
| | if domain == 'QQ_I': |
| | return sympy.polys.domains.QQ_I |
| |
|
| | if domain == 'EX': |
| | return sympy.polys.domains.EX |
| |
|
| | r = cls._re_realfield.match(domain) |
| |
|
| | if r is not None: |
| | _, _, prec = r.groups() |
| |
|
| | if prec is None: |
| | return sympy.polys.domains.RR |
| | else: |
| | return sympy.polys.domains.RealField(int(prec)) |
| |
|
| | r = cls._re_complexfield.match(domain) |
| |
|
| | if r is not None: |
| | _, _, prec = r.groups() |
| |
|
| | if prec is None: |
| | return sympy.polys.domains.CC |
| | else: |
| | return sympy.polys.domains.ComplexField(int(prec)) |
| |
|
| | r = cls._re_finitefield.match(domain) |
| |
|
| | if r is not None: |
| | return sympy.polys.domains.FF(int(r.groups()[1])) |
| |
|
| | r = cls._re_polynomial.match(domain) |
| |
|
| | if r is not None: |
| | ground, gens = r.groups() |
| |
|
| | gens = list(map(sympify, gens.split(','))) |
| |
|
| | if ground in ['Z', 'ZZ']: |
| | return sympy.polys.domains.ZZ.poly_ring(*gens) |
| | elif ground in ['Q', 'QQ']: |
| | return sympy.polys.domains.QQ.poly_ring(*gens) |
| | elif ground in ['R', 'RR']: |
| | return sympy.polys.domains.RR.poly_ring(*gens) |
| | elif ground == 'ZZ_I': |
| | return sympy.polys.domains.ZZ_I.poly_ring(*gens) |
| | elif ground == 'QQ_I': |
| | return sympy.polys.domains.QQ_I.poly_ring(*gens) |
| | else: |
| | return sympy.polys.domains.CC.poly_ring(*gens) |
| |
|
| | r = cls._re_fraction.match(domain) |
| |
|
| | if r is not None: |
| | ground, gens = r.groups() |
| |
|
| | gens = list(map(sympify, gens.split(','))) |
| |
|
| | if ground in ['Z', 'ZZ']: |
| | return sympy.polys.domains.ZZ.frac_field(*gens) |
| | else: |
| | return sympy.polys.domains.QQ.frac_field(*gens) |
| |
|
| | r = cls._re_algebraic.match(domain) |
| |
|
| | if r is not None: |
| | gens = list(map(sympify, r.groups()[1].split(','))) |
| | return sympy.polys.domains.QQ.algebraic_field(*gens) |
| |
|
| | raise OptionError('expected a valid domain specification, got %s' % domain) |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | if 'gens' in options and 'domain' in options and options['domain'].is_Composite and \ |
| | (set(options['domain'].symbols) & set(options['gens'])): |
| | raise GeneratorsError( |
| | "ground domain and generators interfere together") |
| | elif ('gens' not in options or not options['gens']) and \ |
| | 'domain' in options and options['domain'] == sympy.polys.domains.EX: |
| | raise GeneratorsError("you have to provide generators because EX domain was requested") |
| |
|
| |
|
| | class Split(BooleanOption, metaclass=OptionType): |
| | """``split`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'split' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['field', 'greedy', 'domain', 'gaussian', 'extension', |
| | 'modulus', 'symmetric'] |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | if 'split' in options: |
| | raise NotImplementedError("'split' option is not implemented yet") |
| |
|
| |
|
| | class Gaussian(BooleanOption, metaclass=OptionType): |
| | """``gaussian`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'gaussian' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['field', 'greedy', 'domain', 'split', 'extension', |
| | 'modulus', 'symmetric'] |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | if 'gaussian' in options and options['gaussian'] is True: |
| | options['domain'] = sympy.polys.domains.QQ_I |
| | Extension.postprocess(options) |
| |
|
| |
|
| | class Extension(Option, metaclass=OptionType): |
| | """``extension`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'extension' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['greedy', 'domain', 'split', 'gaussian', 'modulus', |
| | 'symmetric'] |
| |
|
| | @classmethod |
| | def preprocess(cls, extension): |
| | if extension == 1: |
| | return bool(extension) |
| | elif extension == 0: |
| | raise OptionError("'False' is an invalid argument for 'extension'") |
| | else: |
| | if not hasattr(extension, '__iter__'): |
| | extension = {extension} |
| | else: |
| | if not extension: |
| | extension = None |
| | else: |
| | extension = set(extension) |
| |
|
| | return extension |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | if 'extension' in options and options['extension'] is not True: |
| | options['domain'] = sympy.polys.domains.QQ.algebraic_field( |
| | *options['extension']) |
| |
|
| |
|
| | class Modulus(Option, metaclass=OptionType): |
| | """``modulus`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'modulus' |
| |
|
| | requires: list[str] = [] |
| | excludes = ['greedy', 'split', 'domain', 'gaussian', 'extension'] |
| |
|
| | @classmethod |
| | def preprocess(cls, modulus): |
| | modulus = sympify(modulus) |
| |
|
| | if modulus.is_Integer and modulus > 0: |
| | return int(modulus) |
| | else: |
| | raise OptionError( |
| | "'modulus' must a positive integer, got %s" % modulus) |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | if 'modulus' in options: |
| | modulus = options['modulus'] |
| | symmetric = options.get('symmetric', True) |
| | options['domain'] = sympy.polys.domains.FF(modulus, symmetric) |
| |
|
| |
|
| | class Symmetric(BooleanOption, metaclass=OptionType): |
| | """``symmetric`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'symmetric' |
| |
|
| | requires = ['modulus'] |
| | excludes = ['greedy', 'domain', 'split', 'gaussian', 'extension'] |
| |
|
| |
|
| | class Strict(BooleanOption, metaclass=OptionType): |
| | """``strict`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'strict' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return True |
| |
|
| |
|
| | class Auto(BooleanOption, Flag, metaclass=OptionType): |
| | """``auto`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'auto' |
| |
|
| | after = ['field', 'domain', 'extension', 'gaussian'] |
| |
|
| | @classmethod |
| | def default(cls): |
| | return True |
| |
|
| | @classmethod |
| | def postprocess(cls, options): |
| | if ('domain' in options or 'field' in options) and 'auto' not in options: |
| | options['auto'] = False |
| |
|
| |
|
| | class Frac(BooleanOption, Flag, metaclass=OptionType): |
| | """``auto`` option to polynomial manipulation functions. """ |
| |
|
| | option = 'frac' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return False |
| |
|
| |
|
| | class Formal(BooleanOption, Flag, metaclass=OptionType): |
| | """``formal`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'formal' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return False |
| |
|
| |
|
| | class Polys(BooleanOption, Flag, metaclass=OptionType): |
| | """``polys`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'polys' |
| |
|
| |
|
| | class Include(BooleanOption, Flag, metaclass=OptionType): |
| | """``include`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'include' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return False |
| |
|
| |
|
| | class All(BooleanOption, Flag, metaclass=OptionType): |
| | """``all`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'all' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return False |
| |
|
| |
|
| | class Gen(Flag, metaclass=OptionType): |
| | """``gen`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'gen' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return 0 |
| |
|
| | @classmethod |
| | def preprocess(cls, gen): |
| | if isinstance(gen, (Basic, int)): |
| | return gen |
| | else: |
| | raise OptionError("invalid argument for 'gen' option") |
| |
|
| |
|
| | class Series(BooleanOption, Flag, metaclass=OptionType): |
| | """``series`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'series' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return False |
| |
|
| |
|
| | class Symbols(Flag, metaclass=OptionType): |
| | """``symbols`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'symbols' |
| |
|
| | @classmethod |
| | def default(cls): |
| | return numbered_symbols('s', start=1) |
| |
|
| | @classmethod |
| | def preprocess(cls, symbols): |
| | if hasattr(symbols, '__iter__'): |
| | return iter(symbols) |
| | else: |
| | raise OptionError("expected an iterator or iterable container, got %s" % symbols) |
| |
|
| |
|
| | class Method(Flag, metaclass=OptionType): |
| | """``method`` flag to polynomial manipulation functions. """ |
| |
|
| | option = 'method' |
| |
|
| | @classmethod |
| | def preprocess(cls, method): |
| | if isinstance(method, str): |
| | return method.lower() |
| | else: |
| | raise OptionError("expected a string, got %s" % method) |
| |
|
| |
|
| | def build_options(gens, args=None): |
| | """Construct options from keyword arguments or ... options. """ |
| | if args is None: |
| | gens, args = (), gens |
| |
|
| | if len(args) != 1 or 'opt' not in args or gens: |
| | return Options(gens, args) |
| | else: |
| | return args['opt'] |
| |
|
| |
|
| | def allowed_flags(args, flags): |
| | """ |
| | Allow specified flags to be used in the given context. |
| | |
| | Examples |
| | ======== |
| | |
| | >>> from sympy.polys.polyoptions import allowed_flags |
| | >>> from sympy.polys.domains import ZZ |
| | |
| | >>> allowed_flags({'domain': ZZ}, []) |
| | |
| | >>> allowed_flags({'domain': ZZ, 'frac': True}, []) |
| | Traceback (most recent call last): |
| | ... |
| | FlagError: 'frac' flag is not allowed in this context |
| | |
| | >>> allowed_flags({'domain': ZZ, 'frac': True}, ['frac']) |
| | |
| | """ |
| | flags = set(flags) |
| |
|
| | for arg in args.keys(): |
| | try: |
| | if Options.__options__[arg].is_Flag and arg not in flags: |
| | raise FlagError( |
| | "'%s' flag is not allowed in this context" % arg) |
| | except KeyError: |
| | raise OptionError("'%s' is not a valid option" % arg) |
| |
|
| |
|
| | def set_defaults(options, **defaults): |
| | """Update options with default values. """ |
| | if 'defaults' not in options: |
| | options = dict(options) |
| | options['defaults'] = defaults |
| |
|
| | return options |
| |
|
| | Options._init_dependencies_order() |
| |
|