mirror of
https://github.com/aykhans/AzSuicideDataVisualization.git
synced 2025-07-02 06:22:25 +00:00
first commit
This commit is contained in:
6
.venv/Lib/site-packages/traitlets/config/__init__.py
Normal file
6
.venv/Lib/site-packages/traitlets/config/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
from .application import *
|
||||
from .configurable import *
|
||||
from .loader import Config
|
1026
.venv/Lib/site-packages/traitlets/config/application.py
Normal file
1026
.venv/Lib/site-packages/traitlets/config/application.py
Normal file
File diff suppressed because it is too large
Load Diff
568
.venv/Lib/site-packages/traitlets/config/configurable.py
Normal file
568
.venv/Lib/site-packages/traitlets/config/configurable.py
Normal file
@ -0,0 +1,568 @@
|
||||
"""A base class for objects that are configurable."""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from copy import deepcopy
|
||||
from textwrap import dedent
|
||||
|
||||
from traitlets.traitlets import (
|
||||
Any,
|
||||
Container,
|
||||
Dict,
|
||||
HasTraits,
|
||||
Instance,
|
||||
default,
|
||||
observe,
|
||||
observe_compat,
|
||||
validate,
|
||||
)
|
||||
from traitlets.utils.text import indent, wrap_paragraphs
|
||||
|
||||
from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Helper classes for Configurables
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class ConfigurableError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MultipleInstanceError(ConfigurableError):
|
||||
pass
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Configurable implementation
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
|
||||
class Configurable(HasTraits):
|
||||
|
||||
config = Instance(Config, (), {})
|
||||
parent = Instance("traitlets.config.configurable.Configurable", allow_none=True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Create a configurable given a config config.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
config : Config
|
||||
If this is empty, default values are used. If config is a
|
||||
:class:`Config` instance, it will be used to configure the
|
||||
instance.
|
||||
parent : Configurable instance, optional
|
||||
The parent Configurable instance of this object.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Subclasses of Configurable must call the :meth:`__init__` method of
|
||||
:class:`Configurable` *before* doing anything else and using
|
||||
:func:`super`::
|
||||
|
||||
class MyConfigurable(Configurable):
|
||||
def __init__(self, config=None):
|
||||
super(MyConfigurable, self).__init__(config=config)
|
||||
# Then any other code you need to finish initialization.
|
||||
|
||||
This ensures that instances will be configured properly.
|
||||
"""
|
||||
parent = kwargs.pop("parent", None)
|
||||
if parent is not None:
|
||||
# config is implied from parent
|
||||
if kwargs.get("config", None) is None:
|
||||
kwargs["config"] = parent.config
|
||||
self.parent = parent
|
||||
|
||||
config = kwargs.pop("config", None)
|
||||
|
||||
# load kwarg traits, other than config
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# record traits set by config
|
||||
config_override_names = set()
|
||||
|
||||
def notice_config_override(change):
|
||||
"""Record traits set by both config and kwargs.
|
||||
|
||||
They will need to be overridden again after loading config.
|
||||
"""
|
||||
if change.name in kwargs:
|
||||
config_override_names.add(change.name)
|
||||
|
||||
self.observe(notice_config_override)
|
||||
|
||||
# load config
|
||||
if config is not None:
|
||||
# We used to deepcopy, but for now we are trying to just save
|
||||
# by reference. This *could* have side effects as all components
|
||||
# will share config. In fact, I did find such a side effect in
|
||||
# _config_changed below. If a config attribute value was a mutable type
|
||||
# all instances of a component were getting the same copy, effectively
|
||||
# making that a class attribute.
|
||||
# self.config = deepcopy(config)
|
||||
self.config = config
|
||||
else:
|
||||
# allow _config_default to return something
|
||||
self._load_config(self.config)
|
||||
self.unobserve(notice_config_override)
|
||||
|
||||
for name in config_override_names:
|
||||
setattr(self, name, kwargs[name])
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Static trait notifiations
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@classmethod
|
||||
def section_names(cls):
|
||||
"""return section names as a list"""
|
||||
return [
|
||||
c.__name__
|
||||
for c in reversed(cls.__mro__)
|
||||
if issubclass(c, Configurable) and issubclass(cls, c)
|
||||
]
|
||||
|
||||
def _find_my_config(self, cfg):
|
||||
"""extract my config from a global Config object
|
||||
|
||||
will construct a Config object of only the config values that apply to me
|
||||
based on my mro(), as well as those of my parent(s) if they exist.
|
||||
|
||||
If I am Bar and my parent is Foo, and their parent is Tim,
|
||||
this will return merge following config sections, in this order::
|
||||
|
||||
[Bar, Foo.Bar, Tim.Foo.Bar]
|
||||
|
||||
With the last item being the highest priority.
|
||||
"""
|
||||
cfgs = [cfg]
|
||||
if self.parent:
|
||||
cfgs.append(self.parent._find_my_config(cfg))
|
||||
my_config = Config()
|
||||
for c in cfgs:
|
||||
for sname in self.section_names():
|
||||
# Don't do a blind getattr as that would cause the config to
|
||||
# dynamically create the section with name Class.__name__.
|
||||
if c._has_section(sname):
|
||||
my_config.merge(c[sname])
|
||||
return my_config
|
||||
|
||||
def _load_config(self, cfg, section_names=None, traits=None):
|
||||
"""load traits from a Config object"""
|
||||
|
||||
if traits is None:
|
||||
traits = self.traits(config=True)
|
||||
if section_names is None:
|
||||
section_names = self.section_names()
|
||||
|
||||
my_config = self._find_my_config(cfg)
|
||||
|
||||
# hold trait notifications until after all config has been loaded
|
||||
with self.hold_trait_notifications():
|
||||
for name, config_value in my_config.items():
|
||||
if name in traits:
|
||||
if isinstance(config_value, LazyConfigValue):
|
||||
# ConfigValue is a wrapper for using append / update on containers
|
||||
# without having to copy the initial value
|
||||
initial = getattr(self, name)
|
||||
config_value = config_value.get_value(initial)
|
||||
elif isinstance(config_value, DeferredConfig):
|
||||
# DeferredConfig tends to come from CLI/environment variables
|
||||
config_value = config_value.get_value(traits[name])
|
||||
# We have to do a deepcopy here if we don't deepcopy the entire
|
||||
# config object. If we don't, a mutable config_value will be
|
||||
# shared by all instances, effectively making it a class attribute.
|
||||
setattr(self, name, deepcopy(config_value))
|
||||
elif not _is_section_key(name) and not isinstance(config_value, Config):
|
||||
from difflib import get_close_matches
|
||||
|
||||
if isinstance(self, LoggingConfigurable):
|
||||
warn = self.log.warning
|
||||
else:
|
||||
warn = lambda msg: warnings.warn(msg, stacklevel=9) # noqa[E371]
|
||||
matches = get_close_matches(name, traits)
|
||||
msg = "Config option `{option}` not recognized by `{klass}`.".format(
|
||||
option=name, klass=self.__class__.__name__
|
||||
)
|
||||
|
||||
if len(matches) == 1:
|
||||
msg += f" Did you mean `{matches[0]}`?"
|
||||
elif len(matches) >= 1:
|
||||
msg += " Did you mean one of: `{matches}`?".format(
|
||||
matches=", ".join(sorted(matches))
|
||||
)
|
||||
warn(msg)
|
||||
|
||||
@observe("config")
|
||||
@observe_compat
|
||||
def _config_changed(self, change):
|
||||
"""Update all the class traits having ``config=True`` in metadata.
|
||||
|
||||
For any class trait with a ``config`` metadata attribute that is
|
||||
``True``, we update the trait with the value of the corresponding
|
||||
config entry.
|
||||
"""
|
||||
# Get all traits with a config metadata entry that is True
|
||||
traits = self.traits(config=True)
|
||||
|
||||
# We auto-load config section for this class as well as any parent
|
||||
# classes that are Configurable subclasses. This starts with Configurable
|
||||
# and works down the mro loading the config for each section.
|
||||
section_names = self.section_names()
|
||||
self._load_config(change.new, traits=traits, section_names=section_names)
|
||||
|
||||
def update_config(self, config):
|
||||
"""Update config and load the new values"""
|
||||
# traitlets prior to 4.2 created a copy of self.config in order to trigger change events.
|
||||
# Some projects (IPython < 5) relied upon one side effect of this,
|
||||
# that self.config prior to update_config was not modified in-place.
|
||||
# For backward-compatibility, we must ensure that self.config
|
||||
# is a new object and not modified in-place,
|
||||
# but config consumers should not rely on this behavior.
|
||||
self.config = deepcopy(self.config)
|
||||
# load config
|
||||
self._load_config(config)
|
||||
# merge it into self.config
|
||||
self.config.merge(config)
|
||||
# TODO: trigger change event if/when dict-update change events take place
|
||||
# DO NOT trigger full trait-change
|
||||
|
||||
@classmethod
|
||||
def class_get_help(cls, inst=None):
|
||||
"""Get the help string for this class in ReST format.
|
||||
|
||||
If `inst` is given, it's current trait values will be used in place of
|
||||
class defaults.
|
||||
"""
|
||||
assert inst is None or isinstance(inst, cls)
|
||||
final_help = []
|
||||
base_classes = ", ".join(p.__name__ for p in cls.__bases__)
|
||||
final_help.append(f"{cls.__name__}({base_classes}) options")
|
||||
final_help.append(len(final_help[0]) * "-")
|
||||
for _, v in sorted(cls.class_traits(config=True).items()):
|
||||
help = cls.class_get_trait_help(v, inst)
|
||||
final_help.append(help)
|
||||
return "\n".join(final_help)
|
||||
|
||||
@classmethod
|
||||
def class_get_trait_help(cls, trait, inst=None, helptext=None):
|
||||
"""Get the helptext string for a single trait.
|
||||
|
||||
:param inst:
|
||||
If given, it's current trait values will be used in place of
|
||||
the class default.
|
||||
:param helptext:
|
||||
If not given, uses the `help` attribute of the current trait.
|
||||
"""
|
||||
assert inst is None or isinstance(inst, cls)
|
||||
lines = []
|
||||
header = f"--{cls.__name__}.{trait.name}"
|
||||
if isinstance(trait, (Container, Dict)):
|
||||
multiplicity = trait.metadata.get("multiplicity", "append")
|
||||
if isinstance(trait, Dict):
|
||||
sample_value = "<key-1>=<value-1>"
|
||||
else:
|
||||
sample_value = "<%s-item-1>" % trait.__class__.__name__.lower()
|
||||
if multiplicity == "append":
|
||||
header = f"{header}={sample_value}..."
|
||||
else:
|
||||
header = f"{header} {sample_value}..."
|
||||
else:
|
||||
header = f"{header}=<{trait.__class__.__name__}>"
|
||||
# header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
|
||||
lines.append(header)
|
||||
|
||||
if helptext is None:
|
||||
helptext = trait.help
|
||||
if helptext != "":
|
||||
helptext = "\n".join(wrap_paragraphs(helptext, 76))
|
||||
lines.append(indent(helptext))
|
||||
|
||||
if "Enum" in trait.__class__.__name__:
|
||||
# include Enum choices
|
||||
lines.append(indent("Choices: %s" % trait.info()))
|
||||
|
||||
if inst is not None:
|
||||
lines.append(indent(f"Current: {getattr(inst, trait.name)!r}"))
|
||||
else:
|
||||
try:
|
||||
dvr = trait.default_value_repr()
|
||||
except Exception:
|
||||
dvr = None # ignore defaults we can't construct
|
||||
if dvr is not None:
|
||||
if len(dvr) > 64:
|
||||
dvr = dvr[:61] + "..."
|
||||
lines.append(indent("Default: %s" % dvr))
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@classmethod
|
||||
def class_print_help(cls, inst=None):
|
||||
"""Get the help string for a single trait and print it."""
|
||||
print(cls.class_get_help(inst))
|
||||
|
||||
@classmethod
|
||||
def _defining_class(cls, trait, classes):
|
||||
"""Get the class that defines a trait
|
||||
|
||||
For reducing redundant help output in config files.
|
||||
Returns the current class if:
|
||||
- the trait is defined on this class, or
|
||||
- the class where it is defined would not be in the config file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
trait : Trait
|
||||
The trait to look for
|
||||
classes : list
|
||||
The list of other classes to consider for redundancy.
|
||||
Will return `cls` even if it is not defined on `cls`
|
||||
if the defining class is not in `classes`.
|
||||
"""
|
||||
defining_cls = cls
|
||||
for parent in cls.mro():
|
||||
if (
|
||||
issubclass(parent, Configurable)
|
||||
and parent in classes
|
||||
and parent.class_own_traits(config=True).get(trait.name, None) is trait
|
||||
):
|
||||
defining_cls = parent
|
||||
return defining_cls
|
||||
|
||||
@classmethod
|
||||
def class_config_section(cls, classes=None):
|
||||
"""Get the config section for this class.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
classes : list, optional
|
||||
The list of other classes in the config file.
|
||||
Used to reduce redundant information.
|
||||
"""
|
||||
|
||||
def c(s):
|
||||
"""return a commented, wrapped block."""
|
||||
s = "\n\n".join(wrap_paragraphs(s, 78))
|
||||
|
||||
return "## " + s.replace("\n", "\n# ")
|
||||
|
||||
# section header
|
||||
breaker = "#" + "-" * 78
|
||||
parent_classes = ", ".join(p.__name__ for p in cls.__bases__ if issubclass(p, Configurable))
|
||||
|
||||
s = f"# {cls.__name__}({parent_classes}) configuration"
|
||||
lines = [breaker, s, breaker]
|
||||
# get the description trait
|
||||
desc = cls.class_traits().get("description")
|
||||
if desc:
|
||||
desc = desc.default_value
|
||||
if not desc:
|
||||
# no description from trait, use __doc__
|
||||
desc = getattr(cls, "__doc__", "")
|
||||
if desc:
|
||||
lines.append(c(desc))
|
||||
lines.append("")
|
||||
|
||||
for name, trait in sorted(cls.class_traits(config=True).items()):
|
||||
default_repr = trait.default_value_repr()
|
||||
|
||||
if classes:
|
||||
defining_class = cls._defining_class(trait, classes)
|
||||
else:
|
||||
defining_class = cls
|
||||
if defining_class is cls:
|
||||
# cls owns the trait, show full help
|
||||
if trait.help:
|
||||
lines.append(c(trait.help))
|
||||
if "Enum" in type(trait).__name__:
|
||||
# include Enum choices
|
||||
lines.append("# Choices: %s" % trait.info())
|
||||
lines.append("# Default: %s" % default_repr)
|
||||
else:
|
||||
# Trait appears multiple times and isn't defined here.
|
||||
# Truncate help to first line + "See also Original.trait"
|
||||
if trait.help:
|
||||
lines.append(c(trait.help.split("\n", 1)[0]))
|
||||
lines.append(f"# See also: {defining_class.__name__}.{name}")
|
||||
|
||||
lines.append(f"# c.{cls.__name__}.{name} = {default_repr}")
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
@classmethod
|
||||
def class_config_rst_doc(cls):
|
||||
"""Generate rST documentation for this class' config options.
|
||||
|
||||
Excludes traits defined on parent classes.
|
||||
"""
|
||||
lines = []
|
||||
classname = cls.__name__
|
||||
for _, trait in sorted(cls.class_traits(config=True).items()):
|
||||
ttype = trait.__class__.__name__
|
||||
|
||||
termline = classname + "." + trait.name
|
||||
|
||||
# Choices or type
|
||||
if "Enum" in ttype:
|
||||
# include Enum choices
|
||||
termline += " : " + trait.info_rst()
|
||||
else:
|
||||
termline += " : " + ttype
|
||||
lines.append(termline)
|
||||
|
||||
# Default value
|
||||
try:
|
||||
dvr = trait.default_value_repr()
|
||||
except Exception:
|
||||
dvr = None # ignore defaults we can't construct
|
||||
if dvr is not None:
|
||||
if len(dvr) > 64:
|
||||
dvr = dvr[:61] + "..."
|
||||
# Double up backslashes, so they get to the rendered docs
|
||||
dvr = dvr.replace("\\n", "\\\\n")
|
||||
lines.append(indent("Default: ``%s``" % dvr))
|
||||
lines.append("")
|
||||
|
||||
help = trait.help or "No description"
|
||||
lines.append(indent(dedent(help)))
|
||||
|
||||
# Blank line
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class LoggingConfigurable(Configurable):
|
||||
"""A parent class for Configurables that log.
|
||||
|
||||
Subclasses have a log trait, and the default behavior
|
||||
is to get the logger from the currently running Application.
|
||||
"""
|
||||
|
||||
log = Any(help="Logger or LoggerAdapter instance")
|
||||
|
||||
@validate("log")
|
||||
def _validate_log(self, proposal):
|
||||
if not isinstance(proposal.value, (logging.Logger, logging.LoggerAdapter)):
|
||||
# warn about unsupported type, but be lenient to allow for duck typing
|
||||
warnings.warn(
|
||||
f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter,"
|
||||
f" got {proposal.value}."
|
||||
)
|
||||
return proposal.value
|
||||
|
||||
@default("log")
|
||||
def _log_default(self):
|
||||
if isinstance(self.parent, LoggingConfigurable):
|
||||
return self.parent.log
|
||||
from traitlets import log
|
||||
|
||||
return log.get_logger()
|
||||
|
||||
def _get_log_handler(self):
|
||||
"""Return the default Handler
|
||||
|
||||
Returns None if none can be found
|
||||
|
||||
Deprecated, this now returns the first log handler which may or may
|
||||
not be the default one.
|
||||
"""
|
||||
logger = self.log
|
||||
if isinstance(logger, logging.LoggerAdapter):
|
||||
logger = logger.logger
|
||||
if not getattr(logger, "handlers", None):
|
||||
# no handlers attribute or empty handlers list
|
||||
return None
|
||||
return logger.handlers[0]
|
||||
|
||||
|
||||
class SingletonConfigurable(LoggingConfigurable):
|
||||
"""A configurable that only allows one instance.
|
||||
|
||||
This class is for classes that should only have one instance of itself
|
||||
or *any* subclass. To create and retrieve such a class use the
|
||||
:meth:`SingletonConfigurable.instance` method.
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
|
||||
@classmethod
|
||||
def _walk_mro(cls):
|
||||
"""Walk the cls.mro() for parent classes that are also singletons
|
||||
|
||||
For use in instance()
|
||||
"""
|
||||
|
||||
for subclass in cls.mro():
|
||||
if (
|
||||
issubclass(cls, subclass)
|
||||
and issubclass(subclass, SingletonConfigurable)
|
||||
and subclass != SingletonConfigurable
|
||||
):
|
||||
yield subclass
|
||||
|
||||
@classmethod
|
||||
def clear_instance(cls):
|
||||
"""unset _instance for this class and singleton parents."""
|
||||
if not cls.initialized():
|
||||
return
|
||||
for subclass in cls._walk_mro():
|
||||
if isinstance(subclass._instance, cls):
|
||||
# only clear instances that are instances
|
||||
# of the calling class
|
||||
subclass._instance = None
|
||||
|
||||
@classmethod
|
||||
def instance(cls, *args, **kwargs):
|
||||
"""Returns a global instance of this class.
|
||||
|
||||
This method create a new instance if none have previously been created
|
||||
and returns a previously created instance is one already exists.
|
||||
|
||||
The arguments and keyword arguments passed to this method are passed
|
||||
on to the :meth:`__init__` method of the class upon instantiation.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a singleton class using instance, and retrieve it::
|
||||
|
||||
>>> from traitlets.config.configurable import SingletonConfigurable
|
||||
>>> class Foo(SingletonConfigurable): pass
|
||||
>>> foo = Foo.instance()
|
||||
>>> foo == Foo.instance()
|
||||
True
|
||||
|
||||
Create a subclass that is retrived using the base class instance::
|
||||
|
||||
>>> class Bar(SingletonConfigurable): pass
|
||||
>>> class Bam(Bar): pass
|
||||
>>> bam = Bam.instance()
|
||||
>>> bam == Bar.instance()
|
||||
True
|
||||
"""
|
||||
# Create and save the instance
|
||||
if cls._instance is None:
|
||||
inst = cls(*args, **kwargs)
|
||||
# Now make sure that the instance will also be returned by
|
||||
# parent classes' _instance attribute.
|
||||
for subclass in cls._walk_mro():
|
||||
subclass._instance = inst
|
||||
|
||||
if isinstance(cls._instance, cls):
|
||||
return cls._instance
|
||||
else:
|
||||
raise MultipleInstanceError(
|
||||
"An incompatible sibling of '%s' is already instanciated"
|
||||
" as singleton: %s" % (cls.__name__, type(cls._instance).__name__)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def initialized(cls):
|
||||
"""Has an instance been created?"""
|
||||
return hasattr(cls, "_instance") and cls._instance is not None
|
1119
.venv/Lib/site-packages/traitlets/config/loader.py
Normal file
1119
.venv/Lib/site-packages/traitlets/config/loader.py
Normal file
File diff suppressed because it is too large
Load Diff
82
.venv/Lib/site-packages/traitlets/config/manager.py
Normal file
82
.venv/Lib/site-packages/traitlets/config/manager.py
Normal file
@ -0,0 +1,82 @@
|
||||
"""Manager to read and modify config data in JSON files.
|
||||
"""
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
import errno
|
||||
import json
|
||||
import os
|
||||
|
||||
from traitlets.config import LoggingConfigurable
|
||||
from traitlets.traitlets import Unicode
|
||||
|
||||
|
||||
def recursive_update(target, new):
|
||||
"""Recursively update one dictionary using another.
|
||||
|
||||
None values will delete their keys.
|
||||
"""
|
||||
for k, v in new.items():
|
||||
if isinstance(v, dict):
|
||||
if k not in target:
|
||||
target[k] = {}
|
||||
recursive_update(target[k], v)
|
||||
if not target[k]:
|
||||
# Prune empty subdicts
|
||||
del target[k]
|
||||
|
||||
elif v is None:
|
||||
target.pop(k, None)
|
||||
|
||||
else:
|
||||
target[k] = v
|
||||
|
||||
|
||||
class BaseJSONConfigManager(LoggingConfigurable):
|
||||
"""General JSON config manager
|
||||
|
||||
Deals with persisting/storing config in a json file
|
||||
"""
|
||||
|
||||
config_dir = Unicode(".")
|
||||
|
||||
def ensure_config_dir_exists(self):
|
||||
try:
|
||||
os.makedirs(self.config_dir, 0o755)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
def file_name(self, section_name):
|
||||
return os.path.join(self.config_dir, section_name + ".json")
|
||||
|
||||
def get(self, section_name):
|
||||
"""Retrieve the config data for the specified section.
|
||||
|
||||
Returns the data as a dictionary, or an empty dictionary if the file
|
||||
doesn't exist.
|
||||
"""
|
||||
filename = self.file_name(section_name)
|
||||
if os.path.isfile(filename):
|
||||
with open(filename, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
return {}
|
||||
|
||||
def set(self, section_name, data):
|
||||
"""Store the given config data."""
|
||||
filename = self.file_name(section_name)
|
||||
self.ensure_config_dir_exists()
|
||||
|
||||
f = open(filename, "w", encoding="utf-8")
|
||||
with f:
|
||||
json.dump(data, f, indent=2)
|
||||
|
||||
def update(self, section_name, new_data):
|
||||
"""Modify the config section by recursively updating it with new_data.
|
||||
|
||||
Returns the modified config data as a dictionary.
|
||||
"""
|
||||
data = self.get(section_name)
|
||||
recursive_update(data, new_data)
|
||||
self.set(section_name, data)
|
||||
return data
|
161
.venv/Lib/site-packages/traitlets/config/sphinxdoc.py
Normal file
161
.venv/Lib/site-packages/traitlets/config/sphinxdoc.py
Normal file
@ -0,0 +1,161 @@
|
||||
"""Machinery for documenting traitlets config options with Sphinx.
|
||||
|
||||
This includes:
|
||||
|
||||
- A Sphinx extension defining directives and roles for config options.
|
||||
- A function to generate an rst file given an Application instance.
|
||||
|
||||
To make this documentation, first set this module as an extension in Sphinx's
|
||||
conf.py::
|
||||
|
||||
extensions = [
|
||||
# ...
|
||||
'traitlets.config.sphinxdoc',
|
||||
]
|
||||
|
||||
Autogenerate the config documentation by running code like this before
|
||||
Sphinx builds::
|
||||
|
||||
from traitlets.config.sphinxdoc import write_doc
|
||||
from myapp import MyApplication
|
||||
|
||||
writedoc('config/options.rst', # File to write
|
||||
'MyApp config options', # Title
|
||||
MyApplication()
|
||||
)
|
||||
|
||||
The generated rST syntax looks like this::
|
||||
|
||||
.. configtrait:: Application.log_datefmt
|
||||
|
||||
Description goes here.
|
||||
|
||||
Cross reference like this: :configtrait:`Application.log_datefmt`.
|
||||
"""
|
||||
from collections import defaultdict
|
||||
from textwrap import dedent
|
||||
|
||||
from traitlets import Undefined
|
||||
from traitlets.utils.text import indent
|
||||
|
||||
|
||||
def setup(app):
|
||||
"""Registers the Sphinx extension.
|
||||
|
||||
You shouldn't need to call this directly; configure Sphinx to use this
|
||||
module instead.
|
||||
"""
|
||||
app.add_object_type("configtrait", "configtrait", objname="Config option")
|
||||
metadata = {"parallel_read_safe": True, "parallel_write_safe": True}
|
||||
return metadata
|
||||
|
||||
|
||||
def interesting_default_value(dv):
|
||||
if (dv is None) or (dv is Undefined):
|
||||
return False
|
||||
if isinstance(dv, (str, list, tuple, dict, set)):
|
||||
return bool(dv)
|
||||
return True
|
||||
|
||||
|
||||
def format_aliases(aliases):
|
||||
fmted = []
|
||||
for a in aliases:
|
||||
dashes = "-" if len(a) == 1 else "--"
|
||||
fmted.append(f"``{dashes}{a}``")
|
||||
return ", ".join(fmted)
|
||||
|
||||
|
||||
def class_config_rst_doc(cls, trait_aliases):
|
||||
"""Generate rST documentation for this class' config options.
|
||||
|
||||
Excludes traits defined on parent classes.
|
||||
"""
|
||||
lines = []
|
||||
classname = cls.__name__
|
||||
for _, trait in sorted(cls.class_traits(config=True).items()):
|
||||
ttype = trait.__class__.__name__
|
||||
|
||||
fullname = classname + "." + trait.name
|
||||
lines += [".. configtrait:: " + fullname, ""]
|
||||
|
||||
help = trait.help.rstrip() or "No description"
|
||||
lines.append(indent(dedent(help)) + "\n")
|
||||
|
||||
# Choices or type
|
||||
if "Enum" in ttype:
|
||||
# include Enum choices
|
||||
lines.append(indent(":options: " + ", ".join("``%r``" % x for x in trait.values)))
|
||||
else:
|
||||
lines.append(indent(":trait type: " + ttype))
|
||||
|
||||
# Default value
|
||||
# Ignore boring default values like None, [] or ''
|
||||
if interesting_default_value(trait.default_value):
|
||||
try:
|
||||
dvr = trait.default_value_repr()
|
||||
except Exception:
|
||||
dvr = None # ignore defaults we can't construct
|
||||
if dvr is not None:
|
||||
if len(dvr) > 64:
|
||||
dvr = dvr[:61] + "..."
|
||||
# Double up backslashes, so they get to the rendered docs
|
||||
dvr = dvr.replace("\\n", "\\\\n")
|
||||
lines.append(indent(":default: ``%s``" % dvr))
|
||||
|
||||
# Command line aliases
|
||||
if trait_aliases[fullname]:
|
||||
fmt_aliases = format_aliases(trait_aliases[fullname])
|
||||
lines.append(indent(":CLI option: " + fmt_aliases))
|
||||
|
||||
# Blank line
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def reverse_aliases(app):
|
||||
"""Produce a mapping of trait names to lists of command line aliases."""
|
||||
res = defaultdict(list)
|
||||
for alias, trait in app.aliases.items():
|
||||
res[trait].append(alias)
|
||||
|
||||
# Flags also often act as aliases for a boolean trait.
|
||||
# Treat flags which set one trait to True as aliases.
|
||||
for flag, (cfg, _) in app.flags.items():
|
||||
if len(cfg) == 1:
|
||||
classname = list(cfg)[0]
|
||||
cls_cfg = cfg[classname]
|
||||
if len(cls_cfg) == 1:
|
||||
traitname = list(cls_cfg)[0]
|
||||
if cls_cfg[traitname] is True:
|
||||
res[classname + "." + traitname].append(flag)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def write_doc(path, title, app, preamble=None):
|
||||
"""Write a rst file documenting config options for a traitlets application.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
The file to be written
|
||||
title : str
|
||||
The human-readable title of the document
|
||||
app : traitlets.config.Application
|
||||
An instance of the application class to be documented
|
||||
preamble : str
|
||||
Extra text to add just after the title (optional)
|
||||
"""
|
||||
trait_aliases = reverse_aliases(app)
|
||||
with open(path, "w") as f:
|
||||
f.write(title + "\n")
|
||||
f.write(("=" * len(title)) + "\n")
|
||||
f.write("\n")
|
||||
if preamble is not None:
|
||||
f.write(preamble + "\n\n")
|
||||
|
||||
for c in app._classes_inc_parents():
|
||||
f.write(class_config_rst_doc(c, trait_aliases))
|
||||
f.write("\n")
|
@ -0,0 +1,853 @@
|
||||
"""
|
||||
Tests for traitlets.config.application.Application
|
||||
"""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from io import StringIO
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
from pytest import mark
|
||||
|
||||
from traitlets import Bool, Bytes, Dict, HasTraits, Integer, List, Set, Tuple, Unicode
|
||||
from traitlets.config.application import Application
|
||||
from traitlets.config.configurable import Configurable
|
||||
from traitlets.config.loader import Config
|
||||
from traitlets.tests.utils import (
|
||||
check_help_all_output,
|
||||
check_help_output,
|
||||
get_output_error_code,
|
||||
)
|
||||
|
||||
try:
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
from unittest import mock
|
||||
|
||||
pjoin = os.path.join
|
||||
|
||||
|
||||
class Foo(Configurable):
|
||||
|
||||
i = Integer(
|
||||
0,
|
||||
help="""
|
||||
The integer i.
|
||||
|
||||
Details about i.
|
||||
""",
|
||||
).tag(config=True)
|
||||
j = Integer(1, help="The integer j.").tag(config=True)
|
||||
name = Unicode("Brian", help="First name.").tag(config=True)
|
||||
la = List([]).tag(config=True)
|
||||
li = List(Integer()).tag(config=True)
|
||||
fdict = Dict().tag(config=True, multiplicity="+")
|
||||
|
||||
|
||||
class Bar(Configurable):
|
||||
|
||||
b = Integer(0, help="The integer b.").tag(config=True)
|
||||
enabled = Bool(True, help="Enable bar.").tag(config=True)
|
||||
tb = Tuple(()).tag(config=True, multiplicity="*")
|
||||
aset = Set().tag(config=True, multiplicity="+")
|
||||
bdict = Dict().tag(config=True)
|
||||
idict = Dict(value_trait=Integer()).tag(config=True)
|
||||
key_dict = Dict(per_key_traits={"i": Integer(), "b": Bytes()}).tag(config=True)
|
||||
|
||||
|
||||
class MyApp(Application):
|
||||
|
||||
name = Unicode("myapp")
|
||||
running = Bool(False, help="Is the app running?").tag(config=True)
|
||||
classes = List([Bar, Foo])
|
||||
config_file = Unicode("", help="Load this config file").tag(config=True)
|
||||
|
||||
warn_tpyo = Unicode(
|
||||
"yes the name is wrong on purpose",
|
||||
config=True,
|
||||
help="Should print a warning if `MyApp.warn-typo=...` command is passed",
|
||||
)
|
||||
|
||||
aliases = {}
|
||||
aliases.update(Application.aliases)
|
||||
aliases.update(
|
||||
{
|
||||
("fooi", "i"): "Foo.i",
|
||||
("j", "fooj"): ("Foo.j", "`j` terse help msg"),
|
||||
"name": "Foo.name",
|
||||
"la": "Foo.la",
|
||||
"li": "Foo.li",
|
||||
"tb": "Bar.tb",
|
||||
"D": "Bar.bdict",
|
||||
"enabled": "Bar.enabled",
|
||||
"enable": "Bar.enabled",
|
||||
"log-level": "Application.log_level",
|
||||
}
|
||||
)
|
||||
|
||||
flags = {}
|
||||
flags.update(Application.flags)
|
||||
flags.update(
|
||||
{
|
||||
("enable", "e"): ({"Bar": {"enabled": True}}, "Set Bar.enabled to True"),
|
||||
("d", "disable"): ({"Bar": {"enabled": False}}, "Set Bar.enabled to False"),
|
||||
"crit": ({"Application": {"log_level": logging.CRITICAL}}, "set level=CRITICAL"),
|
||||
}
|
||||
)
|
||||
|
||||
def init_foo(self):
|
||||
self.foo = Foo(parent=self)
|
||||
|
||||
def init_bar(self):
|
||||
self.bar = Bar(parent=self)
|
||||
|
||||
|
||||
def class_to_names(classes):
|
||||
return [klass.__name__ for klass in classes]
|
||||
|
||||
|
||||
class TestApplication(TestCase):
|
||||
def test_log(self):
|
||||
stream = StringIO()
|
||||
app = MyApp(log_level=logging.INFO)
|
||||
handler = logging.StreamHandler(stream)
|
||||
# trigger reconstruction of the log formatter
|
||||
app.log_format = "%(message)s"
|
||||
app.log_datefmt = "%Y-%m-%d %H:%M"
|
||||
app.log.handlers = [handler]
|
||||
app.log.info("hello")
|
||||
assert "hello" in stream.getvalue()
|
||||
|
||||
def test_no_eval_cli_text(self):
|
||||
app = MyApp()
|
||||
app.initialize(["--Foo.name=1"])
|
||||
app.init_foo()
|
||||
assert app.foo.name == "1"
|
||||
|
||||
def test_basic(self):
|
||||
app = MyApp()
|
||||
self.assertEqual(app.name, "myapp")
|
||||
self.assertEqual(app.running, False)
|
||||
self.assertEqual(app.classes, [MyApp, Bar, Foo])
|
||||
self.assertEqual(app.config_file, "")
|
||||
|
||||
def test_mro_discovery(self):
|
||||
app = MyApp()
|
||||
|
||||
self.assertSequenceEqual(
|
||||
class_to_names(app._classes_with_config_traits()),
|
||||
["Application", "MyApp", "Bar", "Foo"],
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
class_to_names(app._classes_inc_parents()),
|
||||
[
|
||||
"Configurable",
|
||||
"LoggingConfigurable",
|
||||
"SingletonConfigurable",
|
||||
"Application",
|
||||
"MyApp",
|
||||
"Bar",
|
||||
"Foo",
|
||||
],
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(
|
||||
class_to_names(app._classes_with_config_traits([Application])), ["Application"]
|
||||
)
|
||||
self.assertSequenceEqual(
|
||||
class_to_names(app._classes_inc_parents([Application])),
|
||||
["Configurable", "LoggingConfigurable", "SingletonConfigurable", "Application"],
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])), ["Foo"])
|
||||
self.assertSequenceEqual(
|
||||
class_to_names(app._classes_inc_parents([Bar])), ["Configurable", "Bar"]
|
||||
)
|
||||
|
||||
class MyApp2(Application): # no defined `classes` attr
|
||||
pass
|
||||
|
||||
self.assertSequenceEqual(class_to_names(app._classes_with_config_traits([Foo])), ["Foo"])
|
||||
self.assertSequenceEqual(
|
||||
class_to_names(app._classes_inc_parents([Bar])), ["Configurable", "Bar"]
|
||||
)
|
||||
|
||||
def test_config(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(
|
||||
[
|
||||
"--i=10",
|
||||
"--Foo.j=10",
|
||||
"--enable=False",
|
||||
"--log-level=50",
|
||||
]
|
||||
)
|
||||
config = app.config
|
||||
print(config)
|
||||
self.assertEqual(config.Foo.i, 10)
|
||||
self.assertEqual(config.Foo.j, 10)
|
||||
self.assertEqual(config.Bar.enabled, False)
|
||||
self.assertEqual(config.MyApp.log_level, 50)
|
||||
|
||||
def test_config_seq_args(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(
|
||||
"--li 1 --li 3 --la 1 --tb AB 2 --Foo.la=ab --Bar.aset S1 --Bar.aset S2 --Bar.aset S1".split()
|
||||
)
|
||||
assert app.extra_args == ["2"]
|
||||
config = app.config
|
||||
assert config.Foo.li == [1, 3]
|
||||
assert config.Foo.la == ["1", "ab"]
|
||||
assert config.Bar.tb == ("AB",)
|
||||
self.assertEqual(config.Bar.aset, {"S1", "S2"})
|
||||
app.init_foo()
|
||||
assert app.foo.li == [1, 3]
|
||||
assert app.foo.la == ["1", "ab"]
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.aset, {"S1", "S2"})
|
||||
assert app.bar.tb == ("AB",)
|
||||
|
||||
def test_config_dict_args(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(
|
||||
"--Foo.fdict a=1 --Foo.fdict b=b --Foo.fdict c=3 "
|
||||
"--Bar.bdict k=1 -D=a=b -D 22=33 "
|
||||
"--Bar.idict k=1 --Bar.idict b=2 --Bar.idict c=3 ".split()
|
||||
)
|
||||
fdict = {"a": "1", "b": "b", "c": "3"}
|
||||
bdict = {"k": "1", "a": "b", "22": "33"}
|
||||
idict = {"k": 1, "b": 2, "c": 3}
|
||||
config = app.config
|
||||
assert config.Bar.idict == idict
|
||||
self.assertDictEqual(config.Foo.fdict, fdict)
|
||||
self.assertDictEqual(config.Bar.bdict, bdict)
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.fdict, fdict)
|
||||
app.init_bar()
|
||||
assert app.bar.idict == idict
|
||||
self.assertEqual(app.bar.bdict, bdict)
|
||||
|
||||
def test_config_propagation(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--i=10", "--Foo.j=10", "--enable=False", "--log-level=50"])
|
||||
app.init_foo()
|
||||
app.init_bar()
|
||||
self.assertEqual(app.foo.i, 10)
|
||||
self.assertEqual(app.foo.j, 10)
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
|
||||
def test_cli_priority(self):
|
||||
"""Test that loading config files does not override CLI options"""
|
||||
name = "config.py"
|
||||
|
||||
class TestApp(Application):
|
||||
value = Unicode().tag(config=True)
|
||||
config_file_loaded = Bool().tag(config=True)
|
||||
aliases = {"v": "TestApp.value"}
|
||||
|
||||
app = TestApp()
|
||||
with TemporaryDirectory() as td:
|
||||
config_file = pjoin(td, name)
|
||||
with open(config_file, "w") as f:
|
||||
f.writelines(
|
||||
["c.TestApp.value = 'config file'\n", "c.TestApp.config_file_loaded = True\n"]
|
||||
)
|
||||
|
||||
app.parse_command_line(["--v=cli"])
|
||||
assert "value" in app.config.TestApp
|
||||
assert app.config.TestApp.value == "cli"
|
||||
assert app.value == "cli"
|
||||
|
||||
app.load_config_file(name, path=[td])
|
||||
assert app.config_file_loaded
|
||||
assert app.config.TestApp.value == "cli"
|
||||
assert app.value == "cli"
|
||||
|
||||
def test_ipython_cli_priority(self):
|
||||
# this test is almost entirely redundant with above,
|
||||
# but we can keep it around in case of subtle issues creeping into
|
||||
# the exact sequence IPython follows.
|
||||
name = "config.py"
|
||||
|
||||
class TestApp(Application):
|
||||
value = Unicode().tag(config=True)
|
||||
config_file_loaded = Bool().tag(config=True)
|
||||
aliases = {"v": ("TestApp.value", "some help")}
|
||||
|
||||
app = TestApp()
|
||||
with TemporaryDirectory() as td:
|
||||
config_file = pjoin(td, name)
|
||||
with open(config_file, "w") as f:
|
||||
f.writelines(
|
||||
["c.TestApp.value = 'config file'\n", "c.TestApp.config_file_loaded = True\n"]
|
||||
)
|
||||
# follow IPython's config-loading sequence to ensure CLI priority is preserved
|
||||
app.parse_command_line(["--v=cli"])
|
||||
# this is where IPython makes a mistake:
|
||||
# it assumes app.config will not be modified,
|
||||
# and storing a reference is storing a copy
|
||||
cli_config = app.config
|
||||
assert "value" in app.config.TestApp
|
||||
assert app.config.TestApp.value == "cli"
|
||||
assert app.value == "cli"
|
||||
app.load_config_file(name, path=[td])
|
||||
assert app.config_file_loaded
|
||||
# enforce cl-opts override config file opts:
|
||||
# this is where IPython makes a mistake: it assumes
|
||||
# that cl_config is a different object, but it isn't.
|
||||
app.update_config(cli_config)
|
||||
assert app.config.TestApp.value == "cli"
|
||||
assert app.value == "cli"
|
||||
|
||||
def test_cli_allow_none(self):
|
||||
class App(Application):
|
||||
aliases = {"opt": "App.opt"}
|
||||
opt = Unicode(allow_none=True, config=True)
|
||||
|
||||
app = App()
|
||||
app.parse_command_line(["--opt=None"])
|
||||
assert app.opt is None
|
||||
|
||||
def test_flags(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--disable"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["-d"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--enable"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, True)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["-e"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, True)
|
||||
|
||||
def test_flags_help_msg(self):
|
||||
app = MyApp()
|
||||
stdout = io.StringIO()
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
app.print_flag_help()
|
||||
hmsg = stdout.getvalue()
|
||||
self.assertRegex(hmsg, "(?<!-)-e, --enable\\b")
|
||||
self.assertRegex(hmsg, "(?<!-)-d, --disable\\b")
|
||||
self.assertIn("Equivalent to: [--Bar.enabled=True]", hmsg)
|
||||
self.assertIn("Equivalent to: [--Bar.enabled=False]", hmsg)
|
||||
|
||||
def test_aliases(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--i=5", "--j=10"])
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.i, 5)
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.j, 10)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["-i=5", "-j=10"])
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.i, 5)
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.j, 10)
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--fooi=5", "--fooj=10"])
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.i, 5)
|
||||
app.init_foo()
|
||||
self.assertEqual(app.foo.j, 10)
|
||||
|
||||
def test_aliases_help_msg(self):
|
||||
app = MyApp()
|
||||
stdout = io.StringIO()
|
||||
with contextlib.redirect_stdout(stdout):
|
||||
app.print_alias_help()
|
||||
hmsg = stdout.getvalue()
|
||||
self.assertRegex(hmsg, "(?<!-)-i, --fooi\\b")
|
||||
self.assertRegex(hmsg, "(?<!-)-j, --fooj\\b")
|
||||
self.assertIn("Equivalent to: [--Foo.i]", hmsg)
|
||||
self.assertIn("Equivalent to: [--Foo.j]", hmsg)
|
||||
self.assertIn("Equivalent to: [--Foo.name]", hmsg)
|
||||
|
||||
def test_flag_clobber(self):
|
||||
"""test that setting flags doesn't clobber existing settings"""
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--Bar.b=5", "--disable"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
self.assertEqual(app.bar.b, 5)
|
||||
app.parse_command_line(["--enable", "--Bar.b=10"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, True)
|
||||
self.assertEqual(app.bar.b, 10)
|
||||
|
||||
def test_warn_autocorrect(self):
|
||||
stream = StringIO()
|
||||
app = MyApp(log_level=logging.INFO)
|
||||
app.log.handlers = [logging.StreamHandler(stream)]
|
||||
|
||||
cfg = Config()
|
||||
cfg.MyApp.warn_typo = "WOOOO"
|
||||
app.config = cfg
|
||||
|
||||
self.assertIn("warn_typo", stream.getvalue())
|
||||
self.assertIn("warn_tpyo", stream.getvalue())
|
||||
|
||||
def test_flatten_flags(self):
|
||||
cfg = Config()
|
||||
cfg.MyApp.log_level = logging.WARN
|
||||
app = MyApp()
|
||||
app.update_config(cfg)
|
||||
self.assertEqual(app.log_level, logging.WARN)
|
||||
self.assertEqual(app.config.MyApp.log_level, logging.WARN)
|
||||
app.initialize(["--crit"])
|
||||
self.assertEqual(app.log_level, logging.CRITICAL)
|
||||
# this would be app.config.Application.log_level if it failed:
|
||||
self.assertEqual(app.config.MyApp.log_level, logging.CRITICAL)
|
||||
|
||||
def test_flatten_aliases(self):
|
||||
cfg = Config()
|
||||
cfg.MyApp.log_level = logging.WARN
|
||||
app = MyApp()
|
||||
app.update_config(cfg)
|
||||
self.assertEqual(app.log_level, logging.WARN)
|
||||
self.assertEqual(app.config.MyApp.log_level, logging.WARN)
|
||||
app.initialize(["--log-level", "CRITICAL"])
|
||||
self.assertEqual(app.log_level, logging.CRITICAL)
|
||||
# this would be app.config.Application.log_level if it failed:
|
||||
self.assertEqual(app.config.MyApp.log_level, "CRITICAL")
|
||||
|
||||
def test_extra_args(self):
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--Bar.b=5", "extra", "args", "--disable"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, False)
|
||||
self.assertEqual(app.bar.b, 5)
|
||||
self.assertEqual(app.extra_args, ["extra", "args"])
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--Bar.b=5", "--", "extra", "--disable", "args"])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.enabled, True)
|
||||
self.assertEqual(app.bar.b, 5)
|
||||
self.assertEqual(app.extra_args, ["extra", "--disable", "args"])
|
||||
|
||||
app = MyApp()
|
||||
app.parse_command_line(["--disable", "--la", "-", "-", "--Bar.b=1", "--", "-", "extra"])
|
||||
self.assertEqual(app.extra_args, ["-", "-", "extra"])
|
||||
|
||||
def test_unicode_argv(self):
|
||||
app = MyApp()
|
||||
app.parse_command_line(["ünîcødé"])
|
||||
|
||||
def test_document_config_option(self):
|
||||
app = MyApp()
|
||||
app.document_config_options()
|
||||
|
||||
def test_generate_config_file(self):
|
||||
app = MyApp()
|
||||
assert "The integer b." in app.generate_config_file()
|
||||
|
||||
def test_generate_config_file_classes_to_include(self):
|
||||
class NotInConfig(HasTraits):
|
||||
from_hidden = Unicode(
|
||||
"x",
|
||||
help="""From hidden class
|
||||
|
||||
Details about from_hidden.
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
class NoTraits(Foo, Bar, NotInConfig):
|
||||
pass
|
||||
|
||||
app = MyApp()
|
||||
app.classes.append(NoTraits)
|
||||
|
||||
conf_txt = app.generate_config_file()
|
||||
print(conf_txt)
|
||||
self.assertIn("The integer b.", conf_txt)
|
||||
self.assertIn("# Foo(Configurable)", conf_txt)
|
||||
self.assertNotIn("# Configurable", conf_txt)
|
||||
self.assertIn("# NoTraits(Foo, Bar)", conf_txt)
|
||||
|
||||
# inherited traits, parent in class list:
|
||||
self.assertIn("# c.NoTraits.i", conf_txt)
|
||||
self.assertIn("# c.NoTraits.j", conf_txt)
|
||||
self.assertIn("# c.NoTraits.n", conf_txt)
|
||||
self.assertIn("# See also: Foo.j", conf_txt)
|
||||
self.assertIn("# See also: Bar.b", conf_txt)
|
||||
self.assertEqual(conf_txt.count("Details about i."), 1)
|
||||
|
||||
# inherited traits, parent not in class list:
|
||||
self.assertIn("# c.NoTraits.from_hidden", conf_txt)
|
||||
self.assertNotIn("# See also: NotInConfig.", conf_txt)
|
||||
self.assertEqual(conf_txt.count("Details about from_hidden."), 1)
|
||||
self.assertNotIn("NotInConfig", conf_txt)
|
||||
|
||||
def test_multi_file(self):
|
||||
app = MyApp()
|
||||
app.log = logging.getLogger()
|
||||
name = "config.py"
|
||||
with TemporaryDirectory("_1") as td1:
|
||||
with open(pjoin(td1, name), "w") as f1:
|
||||
f1.write("get_config().MyApp.Bar.b = 1")
|
||||
with TemporaryDirectory("_2") as td2:
|
||||
with open(pjoin(td2, name), "w") as f2:
|
||||
f2.write("get_config().MyApp.Bar.b = 2")
|
||||
app.load_config_file(name, path=[td2, td1])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.b, 2)
|
||||
app.load_config_file(name, path=[td1, td2])
|
||||
app.init_bar()
|
||||
self.assertEqual(app.bar.b, 1)
|
||||
|
||||
@mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
|
||||
def test_log_collisions(self):
|
||||
app = MyApp()
|
||||
app.log = logging.getLogger()
|
||||
app.log.setLevel(logging.INFO)
|
||||
name = "config"
|
||||
with TemporaryDirectory("_1") as td:
|
||||
with open(pjoin(td, name + ".py"), "w") as f:
|
||||
f.write("get_config().Bar.b = 1")
|
||||
with open(pjoin(td, name + ".json"), "w") as f:
|
||||
json.dump({"Bar": {"b": 2}}, f)
|
||||
with self.assertLogs(app.log, logging.WARNING) as captured:
|
||||
app.load_config_file(name, path=[td])
|
||||
app.init_bar()
|
||||
assert app.bar.b == 2
|
||||
output = "\n".join(captured.output)
|
||||
assert "Collision" in output
|
||||
assert "1 ignored, using 2" in output
|
||||
assert pjoin(td, name + ".py") in output
|
||||
assert pjoin(td, name + ".json") in output
|
||||
|
||||
@mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
|
||||
def test_log_bad_config(self):
|
||||
app = MyApp()
|
||||
app.log = logging.getLogger()
|
||||
name = "config.py"
|
||||
with TemporaryDirectory() as td:
|
||||
with open(pjoin(td, name), "w") as f:
|
||||
f.write("syntax error()")
|
||||
with self.assertLogs(app.log, logging.ERROR) as captured:
|
||||
app.load_config_file(name, path=[td])
|
||||
output = "\n".join(captured.output)
|
||||
self.assertIn("SyntaxError", output)
|
||||
|
||||
def test_raise_on_bad_config(self):
|
||||
app = MyApp()
|
||||
app.raise_config_file_errors = True
|
||||
app.log = logging.getLogger()
|
||||
name = "config.py"
|
||||
with TemporaryDirectory() as td:
|
||||
with open(pjoin(td, name), "w") as f:
|
||||
f.write("syntax error()")
|
||||
with self.assertRaises(SyntaxError):
|
||||
app.load_config_file(name, path=[td])
|
||||
|
||||
def test_subcommands_instanciation(self):
|
||||
"""Try all ways to specify how to create sub-apps."""
|
||||
app = Root.instance()
|
||||
app.parse_command_line(["sub1"])
|
||||
|
||||
self.assertIsInstance(app.subapp, Sub1)
|
||||
# Check parent hierarchy.
|
||||
self.assertIs(app.subapp.parent, app)
|
||||
|
||||
Root.clear_instance()
|
||||
Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails.
|
||||
app = Root.instance()
|
||||
|
||||
app.parse_command_line(["sub1", "sub2"])
|
||||
self.assertIsInstance(app.subapp, Sub1)
|
||||
self.assertIsInstance(app.subapp.subapp, Sub2)
|
||||
# Check parent hierarchy.
|
||||
self.assertIs(app.subapp.parent, app)
|
||||
self.assertIs(app.subapp.subapp.parent, app.subapp)
|
||||
|
||||
Root.clear_instance()
|
||||
Sub1.clear_instance() # Otherwise, replaced spuriously and hierarchy check fails.
|
||||
app = Root.instance()
|
||||
|
||||
app.parse_command_line(["sub1", "sub3"])
|
||||
self.assertIsInstance(app.subapp, Sub1)
|
||||
self.assertIsInstance(app.subapp.subapp, Sub3)
|
||||
self.assertTrue(app.subapp.subapp.flag) # Set by factory.
|
||||
# Check parent hierarchy.
|
||||
self.assertIs(app.subapp.parent, app)
|
||||
self.assertIs(app.subapp.subapp.parent, app.subapp) # Set by factory.
|
||||
|
||||
def test_loaded_config_files(self):
|
||||
app = MyApp()
|
||||
app.log = logging.getLogger()
|
||||
name = "config.py"
|
||||
with TemporaryDirectory("_1") as td1:
|
||||
config_file = pjoin(td1, name)
|
||||
with open(config_file, "w") as f:
|
||||
f.writelines(["c.MyApp.running = True\n"])
|
||||
|
||||
app.load_config_file(name, path=[td1])
|
||||
self.assertEqual(len(app.loaded_config_files), 1)
|
||||
self.assertEqual(app.loaded_config_files[0], config_file)
|
||||
|
||||
app.start()
|
||||
self.assertEqual(app.running, True)
|
||||
|
||||
# emulate an app that allows dynamic updates and update config file
|
||||
with open(config_file, "w") as f:
|
||||
f.writelines(["c.MyApp.running = False\n"])
|
||||
|
||||
# reload and verify update, and that loaded_configs was not increased
|
||||
app.load_config_file(name, path=[td1])
|
||||
self.assertEqual(len(app.loaded_config_files), 1)
|
||||
self.assertEqual(app.running, False)
|
||||
|
||||
# Attempt to update, ensure error...
|
||||
with self.assertRaises(AttributeError):
|
||||
app.loaded_config_files = "/foo"
|
||||
|
||||
# ensure it can't be udpated via append
|
||||
app.loaded_config_files.append("/bar")
|
||||
self.assertEqual(len(app.loaded_config_files), 1)
|
||||
|
||||
# repeat to ensure no unexpected changes occurred
|
||||
app.load_config_file(name, path=[td1])
|
||||
self.assertEqual(len(app.loaded_config_files), 1)
|
||||
self.assertEqual(app.running, False)
|
||||
|
||||
|
||||
def test_cli_multi_scalar(caplog):
|
||||
class App(Application):
|
||||
aliases = {"opt": "App.opt"}
|
||||
opt = Unicode(config=True)
|
||||
|
||||
app = App(log=logging.getLogger())
|
||||
with pytest.raises(SystemExit):
|
||||
app.parse_command_line(["--opt", "1", "--opt", "2"])
|
||||
record = caplog.get_records("call")[-1]
|
||||
message = record.message
|
||||
|
||||
assert "Error loading argument" in message
|
||||
assert "App.opt=['1', '2']" in message
|
||||
assert "opt only accepts one value" in message
|
||||
assert record.levelno == logging.CRITICAL
|
||||
|
||||
|
||||
class Root(Application):
|
||||
subcommands = {
|
||||
"sub1": ("traitlets.config.tests.test_application.Sub1", "import string"),
|
||||
}
|
||||
|
||||
|
||||
class Sub3(Application):
|
||||
flag = Bool(False)
|
||||
|
||||
|
||||
class Sub2(Application):
|
||||
pass
|
||||
|
||||
|
||||
class Sub1(Application):
|
||||
subcommands = {
|
||||
"sub2": (Sub2, "Application class"),
|
||||
"sub3": (lambda root: Sub3(parent=root, flag=True), "factory"),
|
||||
}
|
||||
|
||||
|
||||
class DeprecatedApp(Application):
|
||||
override_called = False
|
||||
parent_called = False
|
||||
|
||||
def _config_changed(self, name, old, new):
|
||||
self.override_called = True
|
||||
|
||||
def _capture(*args):
|
||||
self.parent_called = True
|
||||
|
||||
with mock.patch.object(self.log, "debug", _capture):
|
||||
super()._config_changed(name, old, new)
|
||||
|
||||
|
||||
def test_deprecated_notifier():
|
||||
app = DeprecatedApp()
|
||||
assert not app.override_called
|
||||
assert not app.parent_called
|
||||
app.config = Config({"A": {"b": "c"}})
|
||||
assert app.override_called
|
||||
assert app.parent_called
|
||||
|
||||
|
||||
def test_help_output():
|
||||
check_help_output(__name__)
|
||||
|
||||
|
||||
def test_help_all_output():
|
||||
check_help_all_output(__name__)
|
||||
|
||||
|
||||
def test_show_config_cli():
|
||||
out, err, ec = get_output_error_code([sys.executable, "-m", __name__, "--show-config"])
|
||||
assert ec == 0
|
||||
assert "show_config" not in out
|
||||
|
||||
|
||||
def test_show_config_json_cli():
|
||||
out, err, ec = get_output_error_code([sys.executable, "-m", __name__, "--show-config-json"])
|
||||
assert ec == 0
|
||||
assert "show_config" not in out
|
||||
|
||||
|
||||
def test_show_config(capsys):
|
||||
cfg = Config()
|
||||
cfg.MyApp.i = 5
|
||||
# don't show empty
|
||||
cfg.OtherApp
|
||||
|
||||
app = MyApp(config=cfg, show_config=True)
|
||||
app.start()
|
||||
out, err = capsys.readouterr()
|
||||
assert "MyApp" in out
|
||||
assert "i = 5" in out
|
||||
assert "OtherApp" not in out
|
||||
|
||||
|
||||
def test_show_config_json(capsys):
|
||||
cfg = Config()
|
||||
cfg.MyApp.i = 5
|
||||
cfg.OtherApp
|
||||
|
||||
app = MyApp(config=cfg, show_config_json=True)
|
||||
app.start()
|
||||
out, err = capsys.readouterr()
|
||||
displayed = json.loads(out)
|
||||
assert Config(displayed) == cfg
|
||||
|
||||
|
||||
def test_deep_alias():
|
||||
from traitlets import Int
|
||||
from traitlets.config import Application, Configurable
|
||||
|
||||
class Foo(Configurable):
|
||||
val = Int(default_value=5).tag(config=True)
|
||||
|
||||
class Bar(Configurable):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.foo = Foo(parent=self)
|
||||
|
||||
class TestApp(Application):
|
||||
name = "test"
|
||||
|
||||
aliases = {"val": "Bar.Foo.val"}
|
||||
classes = [Foo, Bar]
|
||||
|
||||
def initialize(self, *args, **kwargs):
|
||||
super().initialize(*args, **kwargs)
|
||||
self.bar = Bar(parent=self)
|
||||
|
||||
app = TestApp()
|
||||
app.initialize(["--val=10"])
|
||||
assert app.bar.foo.val == 10
|
||||
assert len(list(app.emit_alias_help())) > 0
|
||||
|
||||
|
||||
def test_logging_config(tmp_path, capsys):
|
||||
"""We should be able to configure additional log handlers."""
|
||||
log_file = tmp_path / "log_file"
|
||||
app = Application(
|
||||
logging_config={
|
||||
"version": 1,
|
||||
"handlers": {
|
||||
"file": {
|
||||
"class": "logging.FileHandler",
|
||||
"level": "DEBUG",
|
||||
"filename": str(log_file),
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"Application": {
|
||||
"level": "DEBUG",
|
||||
"handlers": ["console", "file"],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
# the default "console" handler + our new "file" handler
|
||||
assert len(app.log.handlers) == 2
|
||||
|
||||
# log a couple of messages
|
||||
app.log.info("info")
|
||||
app.log.warning("warn")
|
||||
|
||||
# test that log messages get written to the file
|
||||
with open(log_file) as log_handle:
|
||||
assert log_handle.read() == "info\nwarn\n"
|
||||
|
||||
# test that log messages get written to stderr (default console handler)
|
||||
assert capsys.readouterr().err == "[Application] WARNING | warn\n"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def caplogconfig(monkeypatch):
|
||||
"""Capture logging config events for DictConfigurator objects.
|
||||
|
||||
This suppresses the event (so the configuration doesn't happen).
|
||||
|
||||
Returns a list of (args, kwargs).
|
||||
"""
|
||||
calls = []
|
||||
|
||||
def _configure(*args, **kwargs):
|
||||
nonlocal calls
|
||||
calls.append((args, kwargs))
|
||||
|
||||
monkeypatch.setattr(
|
||||
"logging.config.DictConfigurator.configure",
|
||||
_configure,
|
||||
)
|
||||
|
||||
return calls
|
||||
|
||||
|
||||
def test_logging_teardown_on_error(capsys, caplogconfig):
|
||||
"""Ensure we don't try to open logs in order to close them (See #722).
|
||||
|
||||
If you try to configure logging handlers whilst Python is shutting down
|
||||
you may get traceback.
|
||||
"""
|
||||
# create and destroy an app (without configuring logging)
|
||||
# (ensure that the logs were not configured)
|
||||
app = Application()
|
||||
del app
|
||||
assert len(caplogconfig) == 0 # logging was not configured
|
||||
out, err = capsys.readouterr()
|
||||
assert "Traceback" not in err
|
||||
|
||||
# ensure that the logs would have been configured otherwise
|
||||
# (to prevent this test from yielding false-negatives)
|
||||
app = Application()
|
||||
app._logging_configured = True # make it look like logging was configured
|
||||
del app
|
||||
assert len(caplogconfig) == 1 # logging was configured
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# for test_help_output:
|
||||
MyApp.launch_instance()
|
@ -0,0 +1,716 @@
|
||||
"""Tests for traitlets.config.configurable"""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import logging
|
||||
from unittest import TestCase
|
||||
|
||||
from pytest import mark
|
||||
|
||||
from traitlets.config.application import Application
|
||||
from traitlets.config.configurable import (
|
||||
Configurable,
|
||||
LoggingConfigurable,
|
||||
SingletonConfigurable,
|
||||
)
|
||||
from traitlets.config.loader import Config
|
||||
from traitlets.log import get_logger
|
||||
from traitlets.traitlets import (
|
||||
CaselessStrEnum,
|
||||
Dict,
|
||||
Enum,
|
||||
Float,
|
||||
FuzzyEnum,
|
||||
Integer,
|
||||
List,
|
||||
Set,
|
||||
Unicode,
|
||||
_deprecations_shown,
|
||||
validate,
|
||||
)
|
||||
|
||||
from ...tests._warnings import expected_warnings
|
||||
|
||||
|
||||
class MyConfigurable(Configurable):
|
||||
a = Integer(1, help="The integer a.").tag(config=True)
|
||||
b = Float(1.0, help="The integer b.").tag(config=True)
|
||||
c = Unicode("no config")
|
||||
|
||||
|
||||
mc_help = """MyConfigurable(Configurable) options
|
||||
------------------------------------
|
||||
--MyConfigurable.a=<Integer>
|
||||
The integer a.
|
||||
Default: 1
|
||||
--MyConfigurable.b=<Float>
|
||||
The integer b.
|
||||
Default: 1.0"""
|
||||
|
||||
mc_help_inst = """MyConfigurable(Configurable) options
|
||||
------------------------------------
|
||||
--MyConfigurable.a=<Integer>
|
||||
The integer a.
|
||||
Current: 5
|
||||
--MyConfigurable.b=<Float>
|
||||
The integer b.
|
||||
Current: 4.0"""
|
||||
|
||||
# On Python 3, the Integer trait is a synonym for Int
|
||||
mc_help = mc_help.replace("<Integer>", "<Int>")
|
||||
mc_help_inst = mc_help_inst.replace("<Integer>", "<Int>")
|
||||
|
||||
|
||||
class Foo(Configurable):
|
||||
a = Integer(0, help="The integer a.").tag(config=True)
|
||||
b = Unicode("nope").tag(config=True)
|
||||
flist = List([]).tag(config=True)
|
||||
fdict = Dict().tag(config=True)
|
||||
|
||||
|
||||
class Bar(Foo):
|
||||
b = Unicode("gotit", help="The string b.").tag(config=False)
|
||||
c = Float(help="The string c.").tag(config=True)
|
||||
bset = Set([]).tag(config=True, multiplicity="+")
|
||||
bset_values = Set([2, 1, 5]).tag(config=True, multiplicity="+")
|
||||
bdict = Dict().tag(config=True, multiplicity="+")
|
||||
bdict_values = Dict({1: "a", "0": "b", 5: "c"}).tag(config=True, multiplicity="+")
|
||||
|
||||
|
||||
foo_help = """Foo(Configurable) options
|
||||
-------------------------
|
||||
--Foo.a=<Int>
|
||||
The integer a.
|
||||
Default: 0
|
||||
--Foo.b=<Unicode>
|
||||
Default: 'nope'
|
||||
--Foo.fdict=<key-1>=<value-1>...
|
||||
Default: {}
|
||||
--Foo.flist=<list-item-1>...
|
||||
Default: []"""
|
||||
|
||||
bar_help = """Bar(Foo) options
|
||||
----------------
|
||||
--Bar.a=<Int>
|
||||
The integer a.
|
||||
Default: 0
|
||||
--Bar.bdict <key-1>=<value-1>...
|
||||
Default: {}
|
||||
--Bar.bdict_values <key-1>=<value-1>...
|
||||
Default: {1: 'a', '0': 'b', 5: 'c'}
|
||||
--Bar.bset <set-item-1>...
|
||||
Default: set()
|
||||
--Bar.bset_values <set-item-1>...
|
||||
Default: {1, 2, 5}
|
||||
--Bar.c=<Float>
|
||||
The string c.
|
||||
Default: 0.0
|
||||
--Bar.fdict=<key-1>=<value-1>...
|
||||
Default: {}
|
||||
--Bar.flist=<list-item-1>...
|
||||
Default: []"""
|
||||
|
||||
|
||||
class TestConfigurable(TestCase):
|
||||
def test_default(self):
|
||||
c1 = Configurable()
|
||||
c2 = Configurable(config=c1.config)
|
||||
c3 = Configurable(config=c2.config)
|
||||
self.assertEqual(c1.config, c2.config)
|
||||
self.assertEqual(c2.config, c3.config)
|
||||
|
||||
def test_custom(self):
|
||||
config = Config()
|
||||
config.foo = "foo"
|
||||
config.bar = "bar"
|
||||
c1 = Configurable(config=config)
|
||||
c2 = Configurable(config=c1.config)
|
||||
c3 = Configurable(config=c2.config)
|
||||
self.assertEqual(c1.config, config)
|
||||
self.assertEqual(c2.config, config)
|
||||
self.assertEqual(c3.config, config)
|
||||
# Test that copies are not made
|
||||
self.assertTrue(c1.config is config)
|
||||
self.assertTrue(c2.config is config)
|
||||
self.assertTrue(c3.config is config)
|
||||
self.assertTrue(c1.config is c2.config)
|
||||
self.assertTrue(c2.config is c3.config)
|
||||
|
||||
def test_inheritance(self):
|
||||
config = Config()
|
||||
config.MyConfigurable.a = 2
|
||||
config.MyConfigurable.b = 2.0
|
||||
c1 = MyConfigurable(config=config)
|
||||
c2 = MyConfigurable(config=c1.config)
|
||||
self.assertEqual(c1.a, config.MyConfigurable.a)
|
||||
self.assertEqual(c1.b, config.MyConfigurable.b)
|
||||
self.assertEqual(c2.a, config.MyConfigurable.a)
|
||||
self.assertEqual(c2.b, config.MyConfigurable.b)
|
||||
|
||||
def test_parent(self):
|
||||
config = Config()
|
||||
config.Foo.a = 10
|
||||
config.Foo.b = "wow"
|
||||
config.Bar.b = "later"
|
||||
config.Bar.c = 100.0
|
||||
f = Foo(config=config)
|
||||
with expected_warnings(["`b` not recognized"]):
|
||||
b = Bar(config=f.config)
|
||||
self.assertEqual(f.a, 10)
|
||||
self.assertEqual(f.b, "wow")
|
||||
self.assertEqual(b.b, "gotit")
|
||||
self.assertEqual(b.c, 100.0)
|
||||
|
||||
def test_override1(self):
|
||||
config = Config()
|
||||
config.MyConfigurable.a = 2
|
||||
config.MyConfigurable.b = 2.0
|
||||
c = MyConfigurable(a=3, config=config)
|
||||
self.assertEqual(c.a, 3)
|
||||
self.assertEqual(c.b, config.MyConfigurable.b)
|
||||
self.assertEqual(c.c, "no config")
|
||||
|
||||
def test_override2(self):
|
||||
config = Config()
|
||||
config.Foo.a = 1
|
||||
config.Bar.b = "or" # Up above b is config=False, so this won't do it.
|
||||
config.Bar.c = 10.0
|
||||
with expected_warnings(["`b` not recognized"]):
|
||||
c = Bar(config=config)
|
||||
self.assertEqual(c.a, config.Foo.a)
|
||||
self.assertEqual(c.b, "gotit")
|
||||
self.assertEqual(c.c, config.Bar.c)
|
||||
with expected_warnings(["`b` not recognized"]):
|
||||
c = Bar(a=2, b="and", c=20.0, config=config)
|
||||
self.assertEqual(c.a, 2)
|
||||
self.assertEqual(c.b, "and")
|
||||
self.assertEqual(c.c, 20.0)
|
||||
|
||||
def test_help(self):
|
||||
self.assertEqual(MyConfigurable.class_get_help(), mc_help)
|
||||
self.assertEqual(Foo.class_get_help(), foo_help)
|
||||
self.assertEqual(Bar.class_get_help(), bar_help)
|
||||
|
||||
def test_help_inst(self):
|
||||
inst = MyConfigurable(a=5, b=4)
|
||||
self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
|
||||
|
||||
def test_generated_config_enum_comments(self):
|
||||
class MyConf(Configurable):
|
||||
an_enum = Enum("Choice1 choice2".split(), help="Many choices.").tag(config=True)
|
||||
|
||||
help_str = "Many choices."
|
||||
enum_choices_str = "Choices: any of ['Choice1', 'choice2']"
|
||||
rst_choices_str = "MyConf.an_enum : any of ``'Choice1'``|``'choice2'``"
|
||||
or_none_str = "or None"
|
||||
|
||||
cls_help = MyConf.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls_help)
|
||||
self.assertIn(enum_choices_str, cls_help)
|
||||
self.assertNotIn(or_none_str, cls_help)
|
||||
|
||||
cls_cfg = MyConf.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls_cfg)
|
||||
self.assertIn(enum_choices_str, cls_cfg)
|
||||
self.assertNotIn(or_none_str, cls_help)
|
||||
# Check order of Help-msg <--> Choices sections
|
||||
self.assertGreater(cls_cfg.index(enum_choices_str), cls_cfg.index(help_str))
|
||||
|
||||
rst_help = MyConf.class_config_rst_doc()
|
||||
|
||||
self.assertIn(help_str, rst_help)
|
||||
self.assertIn(rst_choices_str, rst_help)
|
||||
self.assertNotIn(or_none_str, rst_help)
|
||||
|
||||
class MyConf2(Configurable):
|
||||
an_enum = Enum(
|
||||
"Choice1 choice2".split(),
|
||||
allow_none=True,
|
||||
default_value="choice2",
|
||||
help="Many choices.",
|
||||
).tag(config=True)
|
||||
|
||||
defaults_str = "Default: 'choice2'"
|
||||
|
||||
cls2_msg = MyConf2.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls2_msg)
|
||||
self.assertIn(enum_choices_str, cls2_msg)
|
||||
self.assertIn(or_none_str, cls2_msg)
|
||||
self.assertIn(defaults_str, cls2_msg)
|
||||
# Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls2_msg.index(defaults_str), cls2_msg.index(enum_choices_str))
|
||||
|
||||
cls2_cfg = MyConf2.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls2_cfg)
|
||||
self.assertIn(enum_choices_str, cls2_cfg)
|
||||
self.assertIn(or_none_str, cls2_cfg)
|
||||
self.assertIn(defaults_str, cls2_cfg)
|
||||
# Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls2_cfg.index(defaults_str), cls2_cfg.index(enum_choices_str))
|
||||
|
||||
def test_generated_config_strenum_comments(self):
|
||||
help_str = "Many choices."
|
||||
defaults_str = "Default: 'choice2'"
|
||||
or_none_str = "or None"
|
||||
|
||||
class MyConf3(Configurable):
|
||||
an_enum = CaselessStrEnum(
|
||||
"Choice1 choice2".split(),
|
||||
allow_none=True,
|
||||
default_value="choice2",
|
||||
help="Many choices.",
|
||||
).tag(config=True)
|
||||
|
||||
enum_choices_str = "Choices: any of ['Choice1', 'choice2'] (case-insensitive)"
|
||||
|
||||
cls3_msg = MyConf3.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls3_msg)
|
||||
self.assertIn(enum_choices_str, cls3_msg)
|
||||
self.assertIn(or_none_str, cls3_msg)
|
||||
self.assertIn(defaults_str, cls3_msg)
|
||||
# Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls3_msg.index(defaults_str), cls3_msg.index(enum_choices_str))
|
||||
|
||||
cls3_cfg = MyConf3.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls3_cfg)
|
||||
self.assertIn(enum_choices_str, cls3_cfg)
|
||||
self.assertIn(or_none_str, cls3_cfg)
|
||||
self.assertIn(defaults_str, cls3_cfg)
|
||||
# Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls3_cfg.index(defaults_str), cls3_cfg.index(enum_choices_str))
|
||||
|
||||
class MyConf4(Configurable):
|
||||
an_enum = FuzzyEnum(
|
||||
"Choice1 choice2".split(),
|
||||
allow_none=True,
|
||||
default_value="choice2",
|
||||
help="Many choices.",
|
||||
).tag(config=True)
|
||||
|
||||
enum_choices_str = "Choices: any case-insensitive prefix of ['Choice1', 'choice2']"
|
||||
|
||||
cls4_msg = MyConf4.class_get_help()
|
||||
|
||||
self.assertIn(help_str, cls4_msg)
|
||||
self.assertIn(enum_choices_str, cls4_msg)
|
||||
self.assertIn(or_none_str, cls4_msg)
|
||||
self.assertIn(defaults_str, cls4_msg)
|
||||
# Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls4_msg.index(defaults_str), cls4_msg.index(enum_choices_str))
|
||||
|
||||
cls4_cfg = MyConf4.class_config_section()
|
||||
|
||||
self.assertIn(help_str, cls4_cfg)
|
||||
self.assertIn(enum_choices_str, cls4_cfg)
|
||||
self.assertIn(or_none_str, cls4_cfg)
|
||||
self.assertIn(defaults_str, cls4_cfg)
|
||||
# Check order of Default <--> Choices sections
|
||||
self.assertGreater(cls4_cfg.index(defaults_str), cls4_cfg.index(enum_choices_str))
|
||||
|
||||
|
||||
class TestSingletonConfigurable(TestCase):
|
||||
def test_instance(self):
|
||||
class Foo(SingletonConfigurable):
|
||||
pass
|
||||
|
||||
self.assertEqual(Foo.initialized(), False)
|
||||
foo = Foo.instance()
|
||||
self.assertEqual(Foo.initialized(), True)
|
||||
self.assertEqual(foo, Foo.instance())
|
||||
self.assertEqual(SingletonConfigurable._instance, None)
|
||||
|
||||
def test_inheritance(self):
|
||||
class Bar(SingletonConfigurable):
|
||||
pass
|
||||
|
||||
class Bam(Bar):
|
||||
pass
|
||||
|
||||
self.assertEqual(Bar.initialized(), False)
|
||||
self.assertEqual(Bam.initialized(), False)
|
||||
bam = Bam.instance()
|
||||
self.assertEqual(Bar.initialized(), True)
|
||||
self.assertEqual(Bam.initialized(), True)
|
||||
self.assertEqual(bam, Bam._instance)
|
||||
self.assertEqual(bam, Bar._instance)
|
||||
self.assertEqual(SingletonConfigurable._instance, None)
|
||||
|
||||
|
||||
class TestLoggingConfigurable(TestCase):
|
||||
def test_parent_logger(self):
|
||||
class Parent(LoggingConfigurable):
|
||||
pass
|
||||
|
||||
class Child(LoggingConfigurable):
|
||||
pass
|
||||
|
||||
log = get_logger().getChild("TestLoggingConfigurable")
|
||||
|
||||
parent = Parent(log=log)
|
||||
child = Child(parent=parent)
|
||||
self.assertEqual(parent.log, log)
|
||||
self.assertEqual(child.log, log)
|
||||
|
||||
parent = Parent()
|
||||
child = Child(parent=parent, log=log)
|
||||
self.assertEqual(parent.log, get_logger())
|
||||
self.assertEqual(child.log, log)
|
||||
|
||||
def test_parent_not_logging_configurable(self):
|
||||
class Parent(Configurable):
|
||||
pass
|
||||
|
||||
class Child(LoggingConfigurable):
|
||||
pass
|
||||
|
||||
parent = Parent()
|
||||
child = Child(parent=parent)
|
||||
self.assertEqual(child.log, get_logger())
|
||||
|
||||
|
||||
class MyParent(Configurable):
|
||||
pass
|
||||
|
||||
|
||||
class MyParent2(MyParent):
|
||||
pass
|
||||
|
||||
|
||||
class TestParentConfigurable(TestCase):
|
||||
def test_parent_config(self):
|
||||
cfg = Config(
|
||||
{
|
||||
"MyParent": {
|
||||
"MyConfigurable": {
|
||||
"b": 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
parent = MyParent(config=cfg)
|
||||
myc = MyConfigurable(parent=parent)
|
||||
self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
|
||||
|
||||
def test_parent_inheritance(self):
|
||||
cfg = Config(
|
||||
{
|
||||
"MyParent": {
|
||||
"MyConfigurable": {
|
||||
"b": 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
parent = MyParent2(config=cfg)
|
||||
myc = MyConfigurable(parent=parent)
|
||||
self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
|
||||
|
||||
def test_multi_parent(self):
|
||||
cfg = Config(
|
||||
{
|
||||
"MyParent2": {
|
||||
"MyParent": {
|
||||
"MyConfigurable": {
|
||||
"b": 2.0,
|
||||
}
|
||||
},
|
||||
# this one shouldn't count
|
||||
"MyConfigurable": {
|
||||
"b": 3.0,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
parent2 = MyParent2(config=cfg)
|
||||
parent = MyParent(parent=parent2)
|
||||
myc = MyConfigurable(parent=parent)
|
||||
self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
|
||||
|
||||
def test_parent_priority(self):
|
||||
cfg = Config(
|
||||
{
|
||||
"MyConfigurable": {
|
||||
"b": 2.0,
|
||||
},
|
||||
"MyParent": {
|
||||
"MyConfigurable": {
|
||||
"b": 3.0,
|
||||
}
|
||||
},
|
||||
"MyParent2": {
|
||||
"MyConfigurable": {
|
||||
"b": 4.0,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
parent = MyParent2(config=cfg)
|
||||
myc = MyConfigurable(parent=parent)
|
||||
self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
|
||||
|
||||
def test_multi_parent_priority(self):
|
||||
cfg = Config(
|
||||
{
|
||||
"MyConfigurable": {
|
||||
"b": 2.0,
|
||||
},
|
||||
"MyParent": {
|
||||
"MyConfigurable": {
|
||||
"b": 3.0,
|
||||
},
|
||||
},
|
||||
"MyParent2": {
|
||||
"MyConfigurable": {
|
||||
"b": 4.0,
|
||||
},
|
||||
"MyParent": {
|
||||
"MyConfigurable": {
|
||||
"b": 5.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
parent2 = MyParent2(config=cfg)
|
||||
parent = MyParent2(parent=parent2)
|
||||
myc = MyConfigurable(parent=parent)
|
||||
self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
|
||||
|
||||
|
||||
class Containers(Configurable):
|
||||
lis = List().tag(config=True)
|
||||
|
||||
def _lis_default(self):
|
||||
return [-1]
|
||||
|
||||
s = Set().tag(config=True)
|
||||
|
||||
def _s_default(self):
|
||||
return {"a"}
|
||||
|
||||
d = Dict().tag(config=True)
|
||||
|
||||
def _d_default(self):
|
||||
return {"a": "b"}
|
||||
|
||||
|
||||
class TestConfigContainers(TestCase):
|
||||
def test_extend(self):
|
||||
c = Config()
|
||||
c.Containers.lis.extend(list(range(5)))
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, list(range(-1, 5)))
|
||||
|
||||
def test_insert(self):
|
||||
c = Config()
|
||||
c.Containers.lis.insert(0, "a")
|
||||
c.Containers.lis.insert(1, "b")
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, ["a", "b", -1])
|
||||
|
||||
def test_prepend(self):
|
||||
c = Config()
|
||||
c.Containers.lis.prepend([1, 2])
|
||||
c.Containers.lis.prepend([2, 3])
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, [2, 3, 1, 2, -1])
|
||||
|
||||
def test_prepend_extend(self):
|
||||
c = Config()
|
||||
c.Containers.lis.prepend([1, 2])
|
||||
c.Containers.lis.extend([2, 3])
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, [1, 2, -1, 2, 3])
|
||||
|
||||
def test_append_extend(self):
|
||||
c = Config()
|
||||
c.Containers.lis.append([1, 2])
|
||||
c.Containers.lis.extend([2, 3])
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, [-1, [1, 2], 2, 3])
|
||||
|
||||
def test_extend_append(self):
|
||||
c = Config()
|
||||
c.Containers.lis.extend([2, 3])
|
||||
c.Containers.lis.append([1, 2])
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, [-1, 2, 3, [1, 2]])
|
||||
|
||||
def test_insert_extend(self):
|
||||
c = Config()
|
||||
c.Containers.lis.insert(0, 1)
|
||||
c.Containers.lis.extend([2, 3])
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.lis, [1, -1, 2, 3])
|
||||
|
||||
def test_set_update(self):
|
||||
c = Config()
|
||||
c.Containers.s.update({0, 1, 2})
|
||||
c.Containers.s.update({3})
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.s, {"a", 0, 1, 2, 3})
|
||||
|
||||
def test_dict_update(self):
|
||||
c = Config()
|
||||
c.Containers.d.update({"c": "d"})
|
||||
c.Containers.d.update({"e": "f"})
|
||||
obj = Containers(config=c)
|
||||
self.assertEqual(obj.d, {"a": "b", "c": "d", "e": "f"})
|
||||
|
||||
def test_update_twice(self):
|
||||
c = Config()
|
||||
c.MyConfigurable.a = 5
|
||||
m = MyConfigurable(config=c)
|
||||
self.assertEqual(m.a, 5)
|
||||
|
||||
c2 = Config()
|
||||
c2.MyConfigurable.a = 10
|
||||
m.update_config(c2)
|
||||
self.assertEqual(m.a, 10)
|
||||
|
||||
c2.MyConfigurable.a = 15
|
||||
m.update_config(c2)
|
||||
self.assertEqual(m.a, 15)
|
||||
|
||||
def test_update_self(self):
|
||||
"""update_config with same config object still triggers config_changed"""
|
||||
c = Config()
|
||||
c.MyConfigurable.a = 5
|
||||
m = MyConfigurable(config=c)
|
||||
self.assertEqual(m.a, 5)
|
||||
c.MyConfigurable.a = 10
|
||||
m.update_config(c)
|
||||
self.assertEqual(m.a, 10)
|
||||
|
||||
def test_config_default(self):
|
||||
class SomeSingleton(SingletonConfigurable):
|
||||
pass
|
||||
|
||||
class DefaultConfigurable(Configurable):
|
||||
a = Integer().tag(config=True)
|
||||
|
||||
def _config_default(self):
|
||||
if SomeSingleton.initialized():
|
||||
return SomeSingleton.instance().config
|
||||
return Config()
|
||||
|
||||
c = Config()
|
||||
c.DefaultConfigurable.a = 5
|
||||
|
||||
d1 = DefaultConfigurable()
|
||||
self.assertEqual(d1.a, 0)
|
||||
|
||||
single = SomeSingleton.instance(config=c)
|
||||
|
||||
d2 = DefaultConfigurable()
|
||||
self.assertIs(d2.config, single.config)
|
||||
self.assertEqual(d2.a, 5)
|
||||
|
||||
def test_config_default_deprecated(self):
|
||||
"""Make sure configurables work even with the deprecations in traitlets"""
|
||||
|
||||
class SomeSingleton(SingletonConfigurable):
|
||||
pass
|
||||
|
||||
# reset deprecation limiter
|
||||
_deprecations_shown.clear()
|
||||
with expected_warnings([]):
|
||||
|
||||
class DefaultConfigurable(Configurable):
|
||||
a = Integer(config=True)
|
||||
|
||||
def _config_default(self):
|
||||
if SomeSingleton.initialized():
|
||||
return SomeSingleton.instance().config
|
||||
return Config()
|
||||
|
||||
c = Config()
|
||||
c.DefaultConfigurable.a = 5
|
||||
|
||||
d1 = DefaultConfigurable()
|
||||
self.assertEqual(d1.a, 0)
|
||||
|
||||
single = SomeSingleton.instance(config=c)
|
||||
|
||||
d2 = DefaultConfigurable()
|
||||
self.assertIs(d2.config, single.config)
|
||||
self.assertEqual(d2.a, 5)
|
||||
|
||||
def test_kwarg_config_priority(self):
|
||||
# a, c set in kwargs
|
||||
# a, b set in config
|
||||
# verify that:
|
||||
# - kwargs are set before config
|
||||
# - kwargs have priority over config
|
||||
class A(Configurable):
|
||||
a = Unicode("default", config=True)
|
||||
b = Unicode("default", config=True)
|
||||
c = Unicode("default", config=True)
|
||||
c_during_config = Unicode("never")
|
||||
|
||||
@validate("b")
|
||||
def _record_c(self, proposal):
|
||||
# setting b from config records c's value at the time
|
||||
self.c_during_config = self.c
|
||||
return proposal.value
|
||||
|
||||
cfg = Config()
|
||||
cfg.A.a = "a-config"
|
||||
cfg.A.b = "b-config"
|
||||
obj = A(a="a-kwarg", c="c-kwarg", config=cfg)
|
||||
assert obj.a == "a-kwarg"
|
||||
assert obj.b == "b-config"
|
||||
assert obj.c == "c-kwarg"
|
||||
assert obj.c_during_config == "c-kwarg"
|
||||
|
||||
|
||||
class TestLogger(TestCase):
|
||||
class A(LoggingConfigurable):
|
||||
foo = Integer(config=True)
|
||||
bar = Integer(config=True)
|
||||
baz = Integer(config=True)
|
||||
|
||||
@mark.skipif(not hasattr(TestCase, "assertLogs"), reason="requires TestCase.assertLogs")
|
||||
def test_warn_match(self):
|
||||
logger = logging.getLogger("test_warn_match")
|
||||
cfg = Config({"A": {"bat": 5}})
|
||||
with self.assertLogs(logger, logging.WARNING) as captured:
|
||||
TestLogger.A(config=cfg, log=logger)
|
||||
|
||||
output = "\n".join(captured.output)
|
||||
self.assertIn("Did you mean one of: `bar, baz`?", output)
|
||||
self.assertIn("Config option `bat` not recognized by `A`.", output)
|
||||
|
||||
cfg = Config({"A": {"fool": 5}})
|
||||
with self.assertLogs(logger, logging.WARNING) as captured:
|
||||
TestLogger.A(config=cfg, log=logger)
|
||||
|
||||
output = "\n".join(captured.output)
|
||||
self.assertIn("Config option `fool` not recognized by `A`.", output)
|
||||
self.assertIn("Did you mean `foo`?", output)
|
||||
|
||||
cfg = Config({"A": {"totally_wrong": 5}})
|
||||
with self.assertLogs(logger, logging.WARNING) as captured:
|
||||
TestLogger.A(config=cfg, log=logger)
|
||||
|
||||
output = "\n".join(captured.output)
|
||||
self.assertIn("Config option `totally_wrong` not recognized by `A`.", output)
|
||||
self.assertNotIn("Did you mean", output)
|
||||
|
||||
|
||||
def test_logger_adapter(caplog, capsys):
|
||||
logger = logging.getLogger("Application")
|
||||
adapter = logging.LoggerAdapter(logger, {"key": "adapted"})
|
||||
|
||||
app = Application(log=adapter, log_level=logging.INFO)
|
||||
app.log_format = "%(key)s %(message)s"
|
||||
app.log.info("test message")
|
||||
|
||||
assert "adapted test message" in capsys.readouterr().err
|
754
.venv/Lib/site-packages/traitlets/config/tests/test_loader.py
Normal file
754
.venv/Lib/site-packages/traitlets/config/tests/test_loader.py
Normal file
@ -0,0 +1,754 @@
|
||||
"""Tests for traitlets.config.loader"""
|
||||
|
||||
# Copyright (c) IPython Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
|
||||
import copy
|
||||
import os
|
||||
import pickle
|
||||
from itertools import chain
|
||||
from tempfile import mkstemp
|
||||
from unittest import TestCase
|
||||
|
||||
import pytest
|
||||
|
||||
from traitlets import Dict, Integer, List, Tuple, Unicode
|
||||
from traitlets.config import Configurable
|
||||
from traitlets.config.loader import (
|
||||
ArgParseConfigLoader,
|
||||
Config,
|
||||
JSONFileConfigLoader,
|
||||
KeyValueConfigLoader,
|
||||
KVArgParseConfigLoader,
|
||||
LazyConfigValue,
|
||||
PyFileConfigLoader,
|
||||
)
|
||||
|
||||
pyfile = """
|
||||
c = get_config()
|
||||
c.a=10
|
||||
c.b=20
|
||||
c.Foo.Bar.value=10
|
||||
c.Foo.Bam.value=list(range(10))
|
||||
c.D.C.value='hi there'
|
||||
"""
|
||||
|
||||
json1file = """
|
||||
{
|
||||
"version": 1,
|
||||
"a": 10,
|
||||
"b": 20,
|
||||
"Foo": {
|
||||
"Bam": {
|
||||
"value": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
|
||||
},
|
||||
"Bar": {
|
||||
"value": 10
|
||||
}
|
||||
},
|
||||
"D": {
|
||||
"C": {
|
||||
"value": "hi there"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
# should not load
|
||||
json2file = """
|
||||
{
|
||||
"version": 2
|
||||
}
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger("devnull")
|
||||
log.setLevel(0)
|
||||
|
||||
|
||||
class TestFileCL(TestCase):
|
||||
def _check_conf(self, config):
|
||||
self.assertEqual(config.a, 10)
|
||||
self.assertEqual(config.b, 20)
|
||||
self.assertEqual(config.Foo.Bar.value, 10)
|
||||
self.assertEqual(config.Foo.Bam.value, list(range(10)))
|
||||
self.assertEqual(config.D.C.value, "hi there")
|
||||
|
||||
def test_python(self):
|
||||
fd, fname = mkstemp(".py", prefix="μnïcø∂e")
|
||||
f = os.fdopen(fd, "w")
|
||||
f.write(pyfile)
|
||||
f.close()
|
||||
# Unlink the file
|
||||
cl = PyFileConfigLoader(fname, log=log)
|
||||
config = cl.load_config()
|
||||
self._check_conf(config)
|
||||
|
||||
def test_json(self):
|
||||
fd, fname = mkstemp(".json", prefix="μnïcø∂e")
|
||||
f = os.fdopen(fd, "w")
|
||||
f.write(json1file)
|
||||
f.close()
|
||||
# Unlink the file
|
||||
cl = JSONFileConfigLoader(fname, log=log)
|
||||
config = cl.load_config()
|
||||
self._check_conf(config)
|
||||
|
||||
def test_context_manager(self):
|
||||
|
||||
fd, fname = mkstemp(".json", prefix="μnïcø∂e")
|
||||
f = os.fdopen(fd, "w")
|
||||
f.write("{}")
|
||||
f.close()
|
||||
|
||||
cl = JSONFileConfigLoader(fname, log=log)
|
||||
|
||||
value = "context_manager"
|
||||
|
||||
with cl as c:
|
||||
c.MyAttr.value = value
|
||||
|
||||
self.assertEqual(cl.config.MyAttr.value, value)
|
||||
|
||||
# check that another loader does see the change
|
||||
_ = JSONFileConfigLoader(fname, log=log)
|
||||
self.assertEqual(cl.config.MyAttr.value, value)
|
||||
|
||||
def test_json_context_bad_write(self):
|
||||
fd, fname = mkstemp(".json", prefix="μnïcø∂e")
|
||||
f = os.fdopen(fd, "w")
|
||||
f.write("{}")
|
||||
f.close()
|
||||
|
||||
with JSONFileConfigLoader(fname, log=log) as config:
|
||||
config.A.b = 1
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
with JSONFileConfigLoader(fname, log=log) as config:
|
||||
config.A.cant_json = lambda x: x
|
||||
|
||||
loader = JSONFileConfigLoader(fname, log=log)
|
||||
cfg = loader.load_config()
|
||||
assert cfg.A.b == 1
|
||||
assert "cant_json" not in cfg.A
|
||||
|
||||
def test_collision(self):
|
||||
a = Config()
|
||||
b = Config()
|
||||
self.assertEqual(a.collisions(b), {})
|
||||
a.A.trait1 = 1
|
||||
b.A.trait2 = 2
|
||||
self.assertEqual(a.collisions(b), {})
|
||||
b.A.trait1 = 1
|
||||
self.assertEqual(a.collisions(b), {})
|
||||
b.A.trait1 = 0
|
||||
self.assertEqual(
|
||||
a.collisions(b),
|
||||
{
|
||||
"A": {
|
||||
"trait1": "1 ignored, using 0",
|
||||
}
|
||||
},
|
||||
)
|
||||
self.assertEqual(
|
||||
b.collisions(a),
|
||||
{
|
||||
"A": {
|
||||
"trait1": "0 ignored, using 1",
|
||||
}
|
||||
},
|
||||
)
|
||||
a.A.trait2 = 3
|
||||
self.assertEqual(
|
||||
b.collisions(a),
|
||||
{
|
||||
"A": {
|
||||
"trait1": "0 ignored, using 1",
|
||||
"trait2": "2 ignored, using 3",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def test_v2raise(self):
|
||||
fd, fname = mkstemp(".json", prefix="μnïcø∂e")
|
||||
f = os.fdopen(fd, "w")
|
||||
f.write(json2file)
|
||||
f.close()
|
||||
# Unlink the file
|
||||
cl = JSONFileConfigLoader(fname, log=log)
|
||||
with self.assertRaises(ValueError):
|
||||
cl.load_config()
|
||||
|
||||
|
||||
def _parse_int_or_str(v):
|
||||
try:
|
||||
return int(v)
|
||||
except Exception:
|
||||
return str(v)
|
||||
|
||||
|
||||
class MyLoader1(ArgParseConfigLoader):
|
||||
def _add_arguments(self, aliases=None, flags=None, classes=None):
|
||||
p = self.parser
|
||||
p.add_argument("-f", "--foo", dest="Global.foo", type=str)
|
||||
p.add_argument("-b", dest="MyClass.bar", type=int)
|
||||
p.add_argument("-n", dest="n", action="store_true")
|
||||
p.add_argument("Global.bam", type=str)
|
||||
p.add_argument("--list1", action="append", type=_parse_int_or_str)
|
||||
p.add_argument("--list2", nargs="+", type=int)
|
||||
|
||||
|
||||
class MyLoader2(ArgParseConfigLoader):
|
||||
def _add_arguments(self, aliases=None, flags=None, classes=None):
|
||||
subparsers = self.parser.add_subparsers(dest="subparser_name")
|
||||
subparser1 = subparsers.add_parser("1")
|
||||
subparser1.add_argument("-x", dest="Global.x")
|
||||
subparser2 = subparsers.add_parser("2")
|
||||
subparser2.add_argument("y")
|
||||
|
||||
|
||||
class TestArgParseCL(TestCase):
|
||||
def test_basic(self):
|
||||
cl = MyLoader1()
|
||||
config = cl.load_config("-f hi -b 10 -n wow".split())
|
||||
self.assertEqual(config.Global.foo, "hi")
|
||||
self.assertEqual(config.MyClass.bar, 10)
|
||||
self.assertEqual(config.n, True)
|
||||
self.assertEqual(config.Global.bam, "wow")
|
||||
config = cl.load_config(["wow"])
|
||||
self.assertEqual(list(config.keys()), ["Global"])
|
||||
self.assertEqual(list(config.Global.keys()), ["bam"])
|
||||
self.assertEqual(config.Global.bam, "wow")
|
||||
|
||||
def test_add_arguments(self):
|
||||
cl = MyLoader2()
|
||||
config = cl.load_config("2 frobble".split())
|
||||
self.assertEqual(config.subparser_name, "2")
|
||||
self.assertEqual(config.y, "frobble")
|
||||
config = cl.load_config("1 -x frobble".split())
|
||||
self.assertEqual(config.subparser_name, "1")
|
||||
self.assertEqual(config.Global.x, "frobble")
|
||||
|
||||
def test_argv(self):
|
||||
cl = MyLoader1(argv="-f hi -b 10 -n wow".split())
|
||||
config = cl.load_config()
|
||||
self.assertEqual(config.Global.foo, "hi")
|
||||
self.assertEqual(config.MyClass.bar, 10)
|
||||
self.assertEqual(config.n, True)
|
||||
self.assertEqual(config.Global.bam, "wow")
|
||||
|
||||
def test_list_args(self):
|
||||
cl = MyLoader1()
|
||||
config = cl.load_config("--list1 1 wow --list2 1 2 3 --list1 B".split())
|
||||
self.assertEqual(list(config.Global.keys()), ["bam"])
|
||||
self.assertEqual(config.Global.bam, "wow")
|
||||
self.assertEqual(config.list1, [1, "B"])
|
||||
self.assertEqual(config.list2, [1, 2, 3])
|
||||
|
||||
|
||||
class C(Configurable):
|
||||
str_trait = Unicode(config=True)
|
||||
int_trait = Integer(config=True)
|
||||
list_trait = List(config=True)
|
||||
list_of_ints = List(Integer(), config=True)
|
||||
dict_trait = Dict(config=True)
|
||||
dict_of_ints = Dict(
|
||||
key_trait=Integer(),
|
||||
value_trait=Integer(),
|
||||
config=True,
|
||||
)
|
||||
dict_multi = Dict(
|
||||
key_trait=Unicode(),
|
||||
per_key_traits={
|
||||
"int": Integer(),
|
||||
"str": Unicode(),
|
||||
},
|
||||
config=True,
|
||||
)
|
||||
|
||||
|
||||
class TestKeyValueCL(TestCase):
|
||||
klass = KeyValueConfigLoader
|
||||
|
||||
def test_eval(self):
|
||||
cl = self.klass(log=log)
|
||||
config = cl.load_config(
|
||||
'--C.str_trait=all --C.int_trait=5 --C.list_trait=["hello",5]'.split()
|
||||
)
|
||||
c = C(config=config)
|
||||
assert c.str_trait == "all"
|
||||
assert c.int_trait == 5
|
||||
assert c.list_trait == ["hello", 5]
|
||||
|
||||
def test_basic(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--" + s[2:] for s in pyfile.split("\n") if s.startswith("c.")]
|
||||
config = cl.load_config(argv)
|
||||
assert config.a == "10"
|
||||
assert config.b == "20"
|
||||
assert config.Foo.Bar.value == "10"
|
||||
# non-literal expressions are not evaluated
|
||||
self.assertEqual(config.Foo.Bam.value, "list(range(10))")
|
||||
self.assertEqual(Unicode().from_string(config.D.C.value), "hi there")
|
||||
|
||||
def test_expanduser(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--a=~/1/2/3", "--b=~", "--c=~/", '--d="~/"']
|
||||
config = cl.load_config(argv)
|
||||
u = Unicode()
|
||||
self.assertEqual(u.from_string(config.a), os.path.expanduser("~/1/2/3"))
|
||||
self.assertEqual(u.from_string(config.b), os.path.expanduser("~"))
|
||||
self.assertEqual(u.from_string(config.c), os.path.expanduser("~/"))
|
||||
self.assertEqual(u.from_string(config.d), "~/")
|
||||
|
||||
def test_extra_args(self):
|
||||
cl = self.klass(log=log)
|
||||
config = cl.load_config(["--a=5", "b", "d", "--c=10"])
|
||||
self.assertEqual(cl.extra_args, ["b", "d"])
|
||||
assert config.a == "5"
|
||||
assert config.c == "10"
|
||||
config = cl.load_config(["--", "--a=5", "--c=10"])
|
||||
self.assertEqual(cl.extra_args, ["--a=5", "--c=10"])
|
||||
|
||||
cl = self.klass(log=log)
|
||||
config = cl.load_config(["extra", "--a=2", "--c=1", "--", "-"])
|
||||
self.assertEqual(cl.extra_args, ["extra", "-"])
|
||||
|
||||
def test_unicode_args(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--a=épsîlön"]
|
||||
config = cl.load_config(argv)
|
||||
print(config, cl.extra_args)
|
||||
self.assertEqual(config.a, "épsîlön")
|
||||
|
||||
def test_list_append(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.list_trait", "x", "--C.list_trait", "y"]
|
||||
config = cl.load_config(argv)
|
||||
assert config.C.list_trait == ["x", "y"]
|
||||
c = C(config=config)
|
||||
assert c.list_trait == ["x", "y"]
|
||||
|
||||
def test_list_single_item(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.list_trait", "x"]
|
||||
config = cl.load_config(argv)
|
||||
c = C(config=config)
|
||||
assert c.list_trait == ["x"]
|
||||
|
||||
def test_dict(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.dict_trait", "x=5", "--C.dict_trait", "y=10"]
|
||||
config = cl.load_config(argv)
|
||||
c = C(config=config)
|
||||
assert c.dict_trait == {"x": "5", "y": "10"}
|
||||
|
||||
def test_dict_key_traits(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--C.dict_of_ints", "1=2", "--C.dict_of_ints", "3=4"]
|
||||
config = cl.load_config(argv)
|
||||
c = C(config=config)
|
||||
assert c.dict_of_ints == {1: 2, 3: 4}
|
||||
|
||||
|
||||
class CBase(Configurable):
|
||||
a = List().tag(config=True)
|
||||
b = List(Integer()).tag(config=True, multiplicity="*")
|
||||
c = List().tag(config=True, multiplicity="append")
|
||||
adict = Dict().tag(config=True)
|
||||
|
||||
|
||||
class CSub(CBase):
|
||||
d = Tuple().tag(config=True)
|
||||
e = Tuple().tag(config=True, multiplicity="+")
|
||||
bdict = Dict().tag(config=True, multiplicity="*")
|
||||
|
||||
|
||||
class TestArgParseKVCL(TestKeyValueCL):
|
||||
klass = KVArgParseConfigLoader
|
||||
|
||||
def test_no_cast_literals(self):
|
||||
cl = self.klass(log=log)
|
||||
# test ipython -c 1 doesn't cast to int
|
||||
argv = ["-c", "1"]
|
||||
config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run"))
|
||||
assert config.IPython.command_to_run == "1"
|
||||
|
||||
def test_int_literals(self):
|
||||
cl = self.klass(log=log)
|
||||
# test ipython -c 1 doesn't cast to int
|
||||
argv = ["-c", "1"]
|
||||
config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run"))
|
||||
assert config.IPython.command_to_run == "1"
|
||||
|
||||
def test_unicode_alias(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["--a=épsîlön"]
|
||||
config = cl.load_config(argv, aliases=dict(a="A.a"))
|
||||
print(dict(config))
|
||||
print(cl.extra_args)
|
||||
print(cl.aliases)
|
||||
self.assertEqual(config.A.a, "épsîlön")
|
||||
|
||||
def test_expanduser2(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["-a", "~/1/2/3", "--b", "'~/1/2/3'"]
|
||||
config = cl.load_config(argv, aliases=dict(a="A.a", b="A.b"))
|
||||
|
||||
class A(Configurable):
|
||||
a = Unicode(config=True)
|
||||
b = Unicode(config=True)
|
||||
|
||||
a = A(config=config)
|
||||
self.assertEqual(a.a, os.path.expanduser("~/1/2/3"))
|
||||
self.assertEqual(a.b, "~/1/2/3")
|
||||
|
||||
def test_eval(self):
|
||||
cl = self.klass(log=log)
|
||||
argv = ["-c", "a=5"]
|
||||
config = cl.load_config(argv, aliases=dict(c="A.c"))
|
||||
self.assertEqual(config.A.c, "a=5")
|
||||
|
||||
def test_seq_traits(self):
|
||||
cl = self.klass(log=log, classes=(CBase, CSub))
|
||||
aliases = {"a3": "CBase.c", "a5": "CSub.e"}
|
||||
argv = (
|
||||
"--CBase.a A --CBase.a 2 --CBase.b 1 --CBase.b 3 --a3 AA --CBase.c BB "
|
||||
"--CSub.d 1 --CSub.d BBB --CSub.e 1 --CSub.e=bcd a b c "
|
||||
).split()
|
||||
config = cl.load_config(argv, aliases=aliases)
|
||||
assert cl.extra_args == ["a", "b", "c"]
|
||||
assert config.CBase.a == ["A", "2"]
|
||||
assert config.CBase.b == [1, 3]
|
||||
self.assertEqual(config.CBase.c, ["AA", "BB"])
|
||||
|
||||
assert config.CSub.d == ("1", "BBB")
|
||||
assert config.CSub.e == ("1", "bcd")
|
||||
|
||||
def test_seq_traits_single_empty_string(self):
|
||||
cl = self.klass(log=log, classes=(CBase,))
|
||||
aliases = {"seqopt": "CBase.c"}
|
||||
argv = ["--seqopt", ""]
|
||||
config = cl.load_config(argv, aliases=aliases)
|
||||
self.assertEqual(config.CBase.c, [""])
|
||||
|
||||
def test_dict_traits(self):
|
||||
cl = self.klass(log=log, classes=(CBase, CSub))
|
||||
aliases = {"D": "CBase.adict", "E": "CSub.bdict"}
|
||||
argv = ["-D", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "-E", "22=222"]
|
||||
config = cl.load_config(argv, aliases=aliases)
|
||||
c = CSub(config=config)
|
||||
assert c.adict == {"k1": "v1", "k2": "2", "k3": "v 3"}
|
||||
assert c.bdict == {"k": "v", "22": "222"}
|
||||
|
||||
def test_mixed_seq_positional(self):
|
||||
aliases = {"c": "Class.trait"}
|
||||
cl = self.klass(log=log, aliases=aliases)
|
||||
assignments = [("-c", "1"), ("--Class.trait=2",), ("--c=3",), ("--Class.trait", "4")]
|
||||
positionals = ["a", "b", "c"]
|
||||
# test with positionals at any index
|
||||
for idx in range(len(assignments) + 1):
|
||||
argv_parts = assignments[:]
|
||||
argv_parts[idx:idx] = (positionals,)
|
||||
argv = list(chain(*argv_parts))
|
||||
|
||||
config = cl.load_config(argv)
|
||||
assert config.Class.trait == ["1", "2", "3", "4"]
|
||||
assert cl.extra_args == ["a", "b", "c"]
|
||||
|
||||
def test_split_positional(self):
|
||||
"""Splitting positionals across flags is no longer allowed in traitlets 5"""
|
||||
cl = self.klass(log=log)
|
||||
argv = ["a", "--Class.trait=5", "b"]
|
||||
with pytest.raises(SystemExit):
|
||||
cl.load_config(argv)
|
||||
|
||||
|
||||
class TestConfig(TestCase):
|
||||
def test_setget(self):
|
||||
c = Config()
|
||||
c.a = 10
|
||||
self.assertEqual(c.a, 10)
|
||||
self.assertEqual("b" in c, False)
|
||||
|
||||
def test_auto_section(self):
|
||||
c = Config()
|
||||
self.assertNotIn("A", c)
|
||||
assert not c._has_section("A")
|
||||
A = c.A
|
||||
A.foo = "hi there"
|
||||
self.assertIn("A", c)
|
||||
assert c._has_section("A")
|
||||
self.assertEqual(c.A.foo, "hi there")
|
||||
del c.A
|
||||
self.assertEqual(c.A, Config())
|
||||
|
||||
def test_merge_doesnt_exist(self):
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
c2.bar = 10
|
||||
c2.Foo.bar = 10
|
||||
c1.merge(c2)
|
||||
self.assertEqual(c1.Foo.bar, 10)
|
||||
self.assertEqual(c1.bar, 10)
|
||||
c2.Bar.bar = 10
|
||||
c1.merge(c2)
|
||||
self.assertEqual(c1.Bar.bar, 10)
|
||||
|
||||
def test_merge_exists(self):
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
c1.Foo.bar = 10
|
||||
c1.Foo.bam = 30
|
||||
c2.Foo.bar = 20
|
||||
c2.Foo.wow = 40
|
||||
c1.merge(c2)
|
||||
self.assertEqual(c1.Foo.bam, 30)
|
||||
self.assertEqual(c1.Foo.bar, 20)
|
||||
self.assertEqual(c1.Foo.wow, 40)
|
||||
c2.Foo.Bam.bam = 10
|
||||
c1.merge(c2)
|
||||
self.assertEqual(c1.Foo.Bam.bam, 10)
|
||||
|
||||
def test_deepcopy(self):
|
||||
c1 = Config()
|
||||
c1.Foo.bar = 10
|
||||
c1.Foo.bam = 30
|
||||
c1.a = "asdf"
|
||||
c1.b = range(10)
|
||||
c1.Test.logger = logging.Logger("test")
|
||||
c1.Test.get_logger = logging.getLogger("test")
|
||||
c2 = copy.deepcopy(c1)
|
||||
self.assertEqual(c1, c2)
|
||||
self.assertTrue(c1 is not c2)
|
||||
self.assertTrue(c1.Foo is not c2.Foo)
|
||||
self.assertTrue(c1.Test is not c2.Test)
|
||||
self.assertTrue(c1.Test.logger is c2.Test.logger)
|
||||
self.assertTrue(c1.Test.get_logger is c2.Test.get_logger)
|
||||
|
||||
def test_builtin(self):
|
||||
c1 = Config()
|
||||
c1.format = "json"
|
||||
|
||||
def test_fromdict(self):
|
||||
c1 = Config({"Foo": {"bar": 1}})
|
||||
self.assertEqual(c1.Foo.__class__, Config)
|
||||
self.assertEqual(c1.Foo.bar, 1)
|
||||
|
||||
def test_fromdictmerge(self):
|
||||
c1 = Config()
|
||||
c2 = Config({"Foo": {"bar": 1}})
|
||||
c1.merge(c2)
|
||||
self.assertEqual(c1.Foo.__class__, Config)
|
||||
self.assertEqual(c1.Foo.bar, 1)
|
||||
|
||||
def test_fromdictmerge2(self):
|
||||
c1 = Config({"Foo": {"baz": 2}})
|
||||
c2 = Config({"Foo": {"bar": 1}})
|
||||
c1.merge(c2)
|
||||
self.assertEqual(c1.Foo.__class__, Config)
|
||||
self.assertEqual(c1.Foo.bar, 1)
|
||||
self.assertEqual(c1.Foo.baz, 2)
|
||||
self.assertNotIn("baz", c2.Foo)
|
||||
|
||||
def test_contains(self):
|
||||
c1 = Config({"Foo": {"baz": 2}})
|
||||
c2 = Config({"Foo": {"bar": 1}})
|
||||
self.assertIn("Foo", c1)
|
||||
self.assertIn("Foo.baz", c1)
|
||||
self.assertIn("Foo.bar", c2)
|
||||
self.assertNotIn("Foo.bar", c1)
|
||||
|
||||
def test_pickle_config(self):
|
||||
cfg = Config()
|
||||
cfg.Foo.bar = 1
|
||||
pcfg = pickle.dumps(cfg)
|
||||
cfg2 = pickle.loads(pcfg)
|
||||
self.assertEqual(cfg2, cfg)
|
||||
|
||||
def test_getattr_section(self):
|
||||
cfg = Config()
|
||||
self.assertNotIn("Foo", cfg)
|
||||
Foo = cfg.Foo
|
||||
assert isinstance(Foo, Config)
|
||||
self.assertIn("Foo", cfg)
|
||||
|
||||
def test_getitem_section(self):
|
||||
cfg = Config()
|
||||
self.assertNotIn("Foo", cfg)
|
||||
Foo = cfg["Foo"]
|
||||
assert isinstance(Foo, Config)
|
||||
self.assertIn("Foo", cfg)
|
||||
|
||||
def test_getattr_not_section(self):
|
||||
cfg = Config()
|
||||
self.assertNotIn("foo", cfg)
|
||||
foo = cfg.foo
|
||||
assert isinstance(foo, LazyConfigValue)
|
||||
self.assertIn("foo", cfg)
|
||||
|
||||
def test_getattr_private_missing(self):
|
||||
cfg = Config()
|
||||
self.assertNotIn("_repr_html_", cfg)
|
||||
with self.assertRaises(AttributeError):
|
||||
_ = cfg._repr_html_
|
||||
self.assertNotIn("_repr_html_", cfg)
|
||||
self.assertEqual(len(cfg), 0)
|
||||
|
||||
def test_lazy_config_repr(self):
|
||||
cfg = Config()
|
||||
cfg.Class.lazy.append(1)
|
||||
cfg_repr = repr(cfg)
|
||||
assert "<LazyConfigValue" in cfg_repr
|
||||
assert "extend" in cfg_repr
|
||||
assert " [1]}>" in cfg_repr
|
||||
assert "value=" not in cfg_repr
|
||||
cfg.Class.lazy.get_value([0])
|
||||
repr2 = repr(cfg)
|
||||
assert repr([0, 1]) in repr2
|
||||
assert "value=" in repr2
|
||||
|
||||
def test_getitem_not_section(self):
|
||||
cfg = Config()
|
||||
self.assertNotIn("foo", cfg)
|
||||
foo = cfg["foo"]
|
||||
assert isinstance(foo, LazyConfigValue)
|
||||
self.assertIn("foo", cfg)
|
||||
|
||||
def test_merge_no_copies(self):
|
||||
c = Config()
|
||||
c2 = Config()
|
||||
c2.Foo.trait = []
|
||||
c.merge(c2)
|
||||
c2.Foo.trait.append(1)
|
||||
self.assertIs(c.Foo, c2.Foo)
|
||||
self.assertEqual(c.Foo.trait, [1])
|
||||
self.assertEqual(c2.Foo.trait, [1])
|
||||
|
||||
def test_merge_multi_lazy(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
If systemwide overwirte and user append, we want both in the right
|
||||
order.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait = [1]
|
||||
c2.Foo.trait.append(2)
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, [1, 2])
|
||||
|
||||
def test_merge_multi_lazyII(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
If both are lazy we still want a lazy config.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.append(1)
|
||||
c2.Foo.trait.append(2)
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait._extend, [1, 2])
|
||||
|
||||
def test_merge_multi_lazy_III(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Prepend should prepend in the right order.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait = [1]
|
||||
c2.Foo.trait.prepend([0])
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, [0, 1])
|
||||
|
||||
def test_merge_multi_lazy_IV(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Both prepending should be lazy
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.prepend([1])
|
||||
c2.Foo.trait.prepend([0])
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait._prepend, [0, 1])
|
||||
|
||||
def test_merge_multi_lazy_update_I(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
dict update shoudl be in the right order.
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait = {"a": 1, "z": 26}
|
||||
c2.Foo.trait.update({"a": 0, "b": 1})
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, {"a": 0, "b": 1, "z": 26})
|
||||
|
||||
def test_merge_multi_lazy_update_II(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Later dict overwrite lazyness
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.update({"a": 0, "b": 1})
|
||||
c2.Foo.trait = {"a": 1, "z": 26}
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait, {"a": 1, "z": 26})
|
||||
|
||||
def test_merge_multi_lazy_update_III(self):
|
||||
"""
|
||||
With multiple config files (systemwide and users), we want compounding.
|
||||
|
||||
Later dict overwrite lazyness
|
||||
"""
|
||||
c1 = Config()
|
||||
c2 = Config()
|
||||
|
||||
c1.Foo.trait.update({"a": 0, "b": 1})
|
||||
c2.Foo.trait.update({"a": 1, "z": 26})
|
||||
|
||||
c = Config()
|
||||
c.merge(c1)
|
||||
c.merge(c2)
|
||||
|
||||
self.assertEqual(c.Foo.trait._update, {"a": 1, "z": 26, "b": 1})
|
Reference in New Issue
Block a user