Fixed existing doctests, added some new ones, enabled doctests in nosetests
This commit is contained in:
parent
279f4014a6
commit
02c3bf8115
5 changed files with 61 additions and 90 deletions
|
|
@ -6,4 +6,4 @@ python:
|
||||||
install:
|
install:
|
||||||
- pip install -e .[develop]
|
- pip install -e .[develop]
|
||||||
script:
|
script:
|
||||||
- nosetests tests/
|
- nosetests --with-doctest
|
||||||
|
|
|
||||||
|
|
@ -584,24 +584,6 @@ class LocalFileStorage(StorageInterface):
|
||||||
Note that for a ``path`` without a trailing slash the last part will be considered a file name and
|
Note that for a ``path`` without a trailing slash the last part will be considered a file name and
|
||||||
hence be returned at second position. If you only need to convert a folder path, be sure to
|
hence be returned at second position. If you only need to convert a folder path, be sure to
|
||||||
include a trailing slash for a string ``path`` or an empty last element for a list ``path``.
|
include a trailing slash for a string ``path`` or an empty last element for a list ``path``.
|
||||||
|
|
||||||
Examples::
|
|
||||||
|
|
||||||
>>> storage = LocalFileStorage("/some/base/folder")
|
|
||||||
>>> storage.sanitize("some/folder/and/some file.gco")
|
|
||||||
("/some/base/folder/some/folder/and", "some_file.gco")
|
|
||||||
>>> storage.sanitize(("some", "folder", "and", "some file.gco"))
|
|
||||||
("/some/base/folder/some/folder/and", "some_file.gco")
|
|
||||||
>>> storage.sanitize("some file.gco")
|
|
||||||
("/some/base/folder", "some_file.gco")
|
|
||||||
>>> storage.sanitize(("some file.gco",))
|
|
||||||
("/some/base/folder", "some_file.gco")
|
|
||||||
>>> storage.sanitize("")
|
|
||||||
("/some/base/folder", "")
|
|
||||||
>>> storage.sanitize("some/folder/with/trailing/slash/")
|
|
||||||
("/some/base/folder/some/folder/with/trailing/slash", "")
|
|
||||||
>>> storage.sanitize("some", "folder", "")
|
|
||||||
("/some/base/folder/some/folder", "")
|
|
||||||
"""
|
"""
|
||||||
name = None
|
name = None
|
||||||
if isinstance(path, (str, unicode, basestring)):
|
if isinstance(path, (str, unicode, basestring)):
|
||||||
|
|
@ -628,24 +610,6 @@ class LocalFileStorage(StorageInterface):
|
||||||
Raises a :class:`ValueError` for a ``name`` containing ``/`` or ``\``. Otherwise strips any characters from the
|
Raises a :class:`ValueError` for a ``name`` containing ``/`` or ``\``. Otherwise strips any characters from the
|
||||||
given ``name`` that are not any of the ASCII characters, digits, ``-``, ``_``, ``.``, ``(``, ``)`` or space and
|
given ``name`` that are not any of the ASCII characters, digits, ``-``, ``_``, ``.``, ``(``, ``)`` or space and
|
||||||
replaces and spaces with ``_``.
|
replaces and spaces with ``_``.
|
||||||
|
|
||||||
Examples::
|
|
||||||
|
|
||||||
>>> storage = LocalFileStorage("/some/base/folder")
|
|
||||||
>>> storage.sanitize_name("some_file.gco")
|
|
||||||
"some_file.gco"
|
|
||||||
>>> storage.sanitize_name("some_file with (parentheses) and ümläuts and digits 123.gco")
|
|
||||||
"some_file_with_(parentheses)_and_mluts_and_digits_123.gco"
|
|
||||||
>>> storage.sanitize_name("pengüino pequeño.stl")
|
|
||||||
"pengino_pequeo.stl"
|
|
||||||
>>> storage.sanitize_name("some/folder/still/left.gco")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in <module>
|
|
||||||
ValueError: name must not contain / or \
|
|
||||||
>>> storage.sanitize_name("also\\no\\backslashes.gco")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in <module>
|
|
||||||
ValueError: name must not contain / or \
|
|
||||||
"""
|
"""
|
||||||
if name is None:
|
if name is None:
|
||||||
return None
|
return None
|
||||||
|
|
@ -664,22 +628,6 @@ class LocalFileStorage(StorageInterface):
|
||||||
Ensures that the on disk representation of ``path`` is located under the configured basefolder. Resolves all
|
Ensures that the on disk representation of ``path`` is located under the configured basefolder. Resolves all
|
||||||
relative path elements (e.g. ``..``) and sanitizes folder names using :func:`sanitize_name`. Final path is the
|
relative path elements (e.g. ``..``) and sanitizes folder names using :func:`sanitize_name`. Final path is the
|
||||||
absolute path including leading ``basefolder`` path.
|
absolute path including leading ``basefolder`` path.
|
||||||
|
|
||||||
Examples::
|
|
||||||
|
|
||||||
>>> storage = LocalFileStorage("/some/base/folder")
|
|
||||||
>>> storage.sanitize_path("folder/with/subfolder")
|
|
||||||
"/some/base/folder/folder/with/subfolder"
|
|
||||||
>>> storage.sanitize_path("folder/with/subfolder/../other/folder")
|
|
||||||
"/some/base/folder/folder/with/other/folder"
|
|
||||||
>>> storage.sanitize_path("/folder/with/leading/slash")
|
|
||||||
"/some/base/folder/folder/with/leading/slash"
|
|
||||||
>>> storage.sanitize_path(".folder/with/leading/dot")
|
|
||||||
"/some/base/folder/folder/with/leading/dot
|
|
||||||
>>> storage.sanitize_path("../../folder/out/of/the/basefolder")
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "<stdin>", line 1, in <module>
|
|
||||||
ValueError: path not contained in base folder: /some/folder/out/of/the/basefolder
|
|
||||||
"""
|
"""
|
||||||
if path[0] == "/" or path[0] == ".":
|
if path[0] == "/" or path[0] == ".":
|
||||||
path = path[1:]
|
path = path[1:]
|
||||||
|
|
|
||||||
|
|
@ -1266,7 +1266,7 @@ class SettingsPlugin(OctoPrintPlugin):
|
||||||
|
|
||||||
# determine diff dict that contains minimal set of changes against the
|
# determine diff dict that contains minimal set of changes against the
|
||||||
# default settings - we only want to persist that, not everything
|
# default settings - we only want to persist that, not everything
|
||||||
diff = octoprint.util.dict_diff(self.get_settings_defaults(), new_current)
|
diff = octoprint.util.dict_minimal_mergediff(self.get_settings_defaults(), new_current)
|
||||||
|
|
||||||
version = self.get_settings_version()
|
version = self.get_settings_version()
|
||||||
|
|
||||||
|
|
@ -1396,7 +1396,7 @@ class SettingsPlugin(OctoPrintPlugin):
|
||||||
# anything already in the settings will be removed from the persisted
|
# anything already in the settings will be removed from the persisted
|
||||||
# config, no need to duplicate it
|
# config, no need to duplicate it
|
||||||
defaults = self.get_settings_defaults()
|
defaults = self.get_settings_defaults()
|
||||||
diff = octoprint.util.dict_diff(defaults, config)
|
diff = octoprint.util.dict_minimal_mergediff(defaults, config)
|
||||||
|
|
||||||
if not diff:
|
if not diff:
|
||||||
# no diff to defaults, no need to have anything persisted
|
# no diff to defaults, no need to have anything persisted
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import re
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from octoprint.settings import settings
|
from octoprint.settings import settings
|
||||||
from octoprint.util import dict_merge, dict_clean, dict_contains_keys
|
from octoprint.util import dict_merge, dict_sanitize, dict_contains_keys
|
||||||
|
|
||||||
class SaveError(Exception):
|
class SaveError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
@ -214,7 +214,7 @@ class PrinterProfileManager(object):
|
||||||
|
|
||||||
identifier = self._sanitize(identifier)
|
identifier = self._sanitize(identifier)
|
||||||
profile["id"] = identifier
|
profile["id"] = identifier
|
||||||
profile = dict_clean(profile, self.__class__.default)
|
profile = dict_sanitize(profile, self.__class__.default)
|
||||||
|
|
||||||
if identifier == "_default":
|
if identifier == "_default":
|
||||||
default_profile = dict_merge(self._load_default(), profile)
|
default_profile = dict_merge(self._load_default(), profile)
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,14 @@ def dict_merge(a, b):
|
||||||
|
|
||||||
Taken from https://www.xormedia.com/recursively-merge-dictionaries-in-python/
|
Taken from https://www.xormedia.com/recursively-merge-dictionaries-in-python/
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> a = dict(foo="foo", bar="bar", fnord=dict(a=1))
|
||||||
|
>>> b = dict(foo="other foo", fnord=dict(b=2, l=["some", "list"]))
|
||||||
|
>>> expected = dict(foo="other foo", bar="bar", fnord=dict(a=1, b=2, l=["some", "list"]))
|
||||||
|
>>> dict_merge(a, b) == expected
|
||||||
|
True
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
a (dict): The dictionary to merge ``b`` into
|
a (dict): The dictionary to merge ``b`` into
|
||||||
b (dict): The dictionary to merge into ``a``
|
b (dict): The dictionary to merge into ``a``
|
||||||
|
|
@ -404,14 +412,24 @@ def dict_merge(a, b):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def dict_clean(a, b):
|
def dict_sanitize(a, b):
|
||||||
"""
|
"""
|
||||||
Recursively deep-cleans ``b`` from ``a``, removing all keys and corresponding values from ``a`` that appear in
|
Recursively deep-sanitizes ``a`` based on ``b``, removing all keys (and
|
||||||
``b``.
|
associated values) from ``a`` that do not appear in ``b``.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> a = dict(foo="foo", bar="bar", fnord=dict(a=1, b=2, l=["some", "list"]))
|
||||||
|
>>> b = dict(foo=None, fnord=dict(a=None, b=None))
|
||||||
|
>>> expected = dict(foo="foo", fnord=dict(a=1, b=2))
|
||||||
|
>>> dict_sanitize(a, b) == expected
|
||||||
|
True
|
||||||
|
>>> dict_clean(a, b) == expected
|
||||||
|
True
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
a (dict): The dictionary to clean from ``b``.
|
a (dict): The dictionary to clean against ``b``.
|
||||||
b (dict): The dictionary to clean ``b`` from.
|
b (dict): The dictionary containing the key structure to clean from ``a``.
|
||||||
|
|
||||||
Results:
|
Results:
|
||||||
dict: A new dict based on ``a`` with all keys (and corresponding values) found in ``b`` removed.
|
dict: A new dict based on ``a`` with all keys (and corresponding values) found in ``b`` removed.
|
||||||
|
|
@ -426,13 +444,15 @@ def dict_clean(a, b):
|
||||||
if not k in b:
|
if not k in b:
|
||||||
del result[k]
|
del result[k]
|
||||||
elif isinstance(v, dict):
|
elif isinstance(v, dict):
|
||||||
result[k] = dict_clean(v, b[k])
|
result[k] = dict_sanitize(v, b[k])
|
||||||
else:
|
else:
|
||||||
result[k] = deepcopy(v)
|
result[k] = deepcopy(v)
|
||||||
return result
|
return result
|
||||||
|
dict_clean = deprecated("dict_clean has been renamed to dict_sanitize",
|
||||||
|
includedoc="Replaced by :func:`dict_sanitize`")(dict_sanitize)
|
||||||
|
|
||||||
|
|
||||||
def dict_diff(a, b):
|
def dict_minimal_mergediff(source, target):
|
||||||
"""
|
"""
|
||||||
Recursively calculates the minimal dict that would be needed to be deep merged with
|
Recursively calculates the minimal dict that would be needed to be deep merged with
|
||||||
a in order to produce the same result as deep merging a and b.
|
a in order to produce the same result as deep merging a and b.
|
||||||
|
|
@ -441,67 +461,70 @@ def dict_diff(a, b):
|
||||||
|
|
||||||
>>> a = dict(foo=dict(a=1, b=2), bar=dict(c=3, d=4))
|
>>> a = dict(foo=dict(a=1, b=2), bar=dict(c=3, d=4))
|
||||||
>>> b = dict(bar=dict(c=3, d=5), fnord=None)
|
>>> b = dict(bar=dict(c=3, d=5), fnord=None)
|
||||||
>>> c = dict_diff(a, b)
|
>>> c = dict_minimal_mergediff(a, b)
|
||||||
>>> c == dict(bar=dict(d=5), fnord=None)
|
>>> c == dict(bar=dict(d=5), fnord=None)
|
||||||
True
|
True
|
||||||
>>> dict_merge(a, c) == dict_merge(a, b)
|
>>> dict_merge(a, c) == dict_merge(a, b)
|
||||||
True
|
True
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
a (dict): Source dictionary
|
source (dict): Source dictionary
|
||||||
b (dict): Dictionary to compare to source dictionary and derive diff for
|
target (dict): Dictionary to compare to source dictionary and derive diff for
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: The minimal dictionary to deep merge on a to get the same result
|
dict: The minimal dictionary to deep merge on ``source`` to get the same result
|
||||||
as deep merging b on a.
|
as deep merging ``target`` on ``source``.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(a, dict) or not isinstance(b, dict):
|
if not isinstance(source, dict) or not isinstance(target, dict):
|
||||||
raise ValueError("a and b must be dictionaries")
|
raise ValueError("source and target must be dictionaries")
|
||||||
|
|
||||||
if a == b:
|
if source == target:
|
||||||
# shortcut: if both are equal, we return an empty dict as result
|
# shortcut: if both are equal, we return an empty dict as result
|
||||||
return dict()
|
return dict()
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
|
|
||||||
all_keys = set(a.keys() + b.keys())
|
all_keys = set(source.keys() + target.keys())
|
||||||
result = dict()
|
result = dict()
|
||||||
for k in all_keys:
|
for k in all_keys:
|
||||||
if k not in b:
|
if k not in target:
|
||||||
# key not contained in b => not contained in result
|
# key not contained in b => not contained in result
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if k in a:
|
if k in source:
|
||||||
# key is present in both dicts, we have to take a look at the value
|
# key is present in both dicts, we have to take a look at the value
|
||||||
value_a = a[k]
|
value_source = source[k]
|
||||||
value_b = b[k]
|
value_target = target[k]
|
||||||
|
|
||||||
if value_a != value_b:
|
if value_source != value_target:
|
||||||
# we only need to look further if the values are not equal
|
# we only need to look further if the values are not equal
|
||||||
|
|
||||||
if isinstance(value_a, dict) and isinstance(value_b, dict):
|
if isinstance(value_source, dict) and isinstance(value_target, dict):
|
||||||
# both are dicts => deeper down it goes into the rabbit hole
|
# both are dicts => deeper down it goes into the rabbit hole
|
||||||
result[k] = dict_diff(value_a, value_b)
|
result[k] = dict_minimal_mergediff(value_source, value_target)
|
||||||
else:
|
else:
|
||||||
# new b wins over old a
|
# new b wins over old a
|
||||||
result[k] = deepcopy(value_b)
|
result[k] = deepcopy(value_target)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# key is new, add it
|
# key is new, add it
|
||||||
result[k] = deepcopy(b[k])
|
result[k] = deepcopy(target[k])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def dict_contains_keys(a, b):
|
def dict_contains_keys(keys, dictionary):
|
||||||
"""
|
"""
|
||||||
Recursively deep-checks if ``a`` contains all keys found in ``b``.
|
Recursively deep-checks if ``dictionary`` contains all keys found in ``keys``.
|
||||||
|
|
||||||
Example::
|
Example::
|
||||||
|
|
||||||
>>> dict_contains_keys(dict(foo="bar", fnord=dict(a=1, b=2, c=3)), dict(foo="some_other_bar", fnord=dict(b=100)))
|
>>> positive = dict(foo="some_other_bar", fnord=dict(b=100))
|
||||||
|
>>> negative = dict(foo="some_other_bar", fnord=dict(b=100, d=20))
|
||||||
|
>>> dictionary = dict(foo="bar", fnord=dict(a=1, b=2, c=3))
|
||||||
|
>>> dict_contains_keys(positive, dictionary)
|
||||||
True
|
True
|
||||||
>>> dict_contains_keys(dict(foo="bar", fnord=dict(a=1, b=2, c=3)), dict(foo="some_other_bar", fnord=dict(b=100, d=20)))
|
>>> dict_contains_keys(negative, dictionary)
|
||||||
False
|
False
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
|
|
@ -512,14 +535,14 @@ def dict_contains_keys(a, b):
|
||||||
boolean: True if all keys found in ``b`` are also present in ``a``, False otherwise.
|
boolean: True if all keys found in ``b`` are also present in ``a``, False otherwise.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not isinstance(a, dict) or not isinstance(b, dict):
|
if not isinstance(keys, dict) or not isinstance(dictionary, dict):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for k, v in a.iteritems():
|
for k, v in keys.iteritems():
|
||||||
if not k in b:
|
if not k in dictionary:
|
||||||
return False
|
return False
|
||||||
elif isinstance(v, dict):
|
elif isinstance(v, dict):
|
||||||
if not dict_contains_keys(v, b[k]):
|
if not dict_contains_keys(v, dictionary[k]):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue