From f772c9a9bed1af9448f1e8afa13a45e787f5d435 Mon Sep 17 00:00:00 2001 From: Chris Lamb Date: Tue, 2 Jun 2020 15:17:23 +0100 Subject: [PATCH 1/2] Ensure default Set values are sorted for a reproducible build Whilst working on the Reproducible Builds effort [0] we noticed that traitlets generates non-reproducible output which is affecting the reproducibility of other packages. For example, in nbconvert: -

Default: {'image/jpeg', 'image/svg+xml', [..] +

Default: {'image/svg+xml', 'application/pdf', [..] (From https://tests.reproducible-builds.org/debian/rb-pkg/unstable/ amd64/nbconvert.html on 20191014) This is due to it not iterating over a Set traitlet type in a deterministic ordering when generating the "Default:" human-readable string. This patch ensures that we display the Set traits defaults in a sorted, and thus reproducible, manner. [0] https://reproducible-builds.org/ --- traitlets/traitlets.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index c53b934f..42cf4ef8 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -2596,6 +2596,10 @@ def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, """ super(Set, self).__init__(trait, default_value, minlen, maxlen, **kwargs) + def default_value_repr(self): + # Ensure default value is sorted for a reproducible build + return repr(sorted(self.make_dynamic_default())) + class Tuple(Container): """An instance of a Python tuple.""" From df72b2870b5c1f08d88106093d3a01476f84160e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 11 Aug 2020 11:17:59 -0700 Subject: [PATCH 2/2] Ensure help is reproducible as well. --- traitlets/config/tests/test_configurable.py | 6 ++++++ traitlets/traitlets.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/traitlets/config/tests/test_configurable.py b/traitlets/config/tests/test_configurable.py index 35f42f51..14d85f80 100644 --- a/traitlets/config/tests/test_configurable.py +++ b/traitlets/config/tests/test_configurable.py @@ -62,7 +62,9 @@ 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=u"""Foo(Configurable) options ------------------------- @@ -83,8 +85,12 @@ class Bar(Foo): Default: 0 --Bar.bdict =... Default: {} +--Bar.bdict_values =... + Default: {1: 'a', '0': 'b', 5: 'c'} --Bar.bset ... Default: set() +--Bar.bset_values ... + Default: {1, 2, 5} --Bar.c= The string c. Default: 0.0 diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 42cf4ef8..3c8629ff 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -2598,7 +2598,10 @@ def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, def default_value_repr(self): # Ensure default value is sorted for a reproducible build - return repr(sorted(self.make_dynamic_default())) + list_repr = repr(sorted(self.make_dynamic_default())) + if list_repr == '[]': + return 'set()' + return '{'+list_repr[1:-1]+'}' class Tuple(Container):