def unnamed_subcollections(self): subcoll = Collection() named_subcoll = Collection("hello") # We're binding to name 'subcoll', but subcoll itself has no .name # attribute/value, which is what's being tested. When bug present, # that fact will cause serialized() to die on sorted() when # comparing to named_subcoll (which has a string name). root = Collection(named_subcoll, subcoll=subcoll) expected = dict( name=None, default=None, help=None, tasks=[], collections=[ # Expect anonymous first since we sort them as if their # name was the empty string. dict( tasks=[], collections=[], name=None, default=None, help=None, ), dict( tasks=[], collections=[], name="hello", default=None, help=None, ), ], ) assert expected == root.serialized()
def subcollection_paths_may_be_dotted(self): leaf = Collection("leaf", self.task) leaf.configure({"key": "leaf-value"}) middle = Collection("middle", leaf) root = Collection("root", middle) config = root.configuration("middle.leaf.task") assert config == {"key": "leaf-value"}
def raises_ValueError_if_collection_without_name(self): # Aka non-root collections must either have an explicit name given # via kwarg, have a name attribute set, or be a module with # __name__ defined. root = Collection() sub = Collection() with raises(ValueError): root.add_collection(sub)
def percolates_to_subcollection_names(self): @task def my_task(c): pass coll = Collection(inner_coll=Collection(my_task)) contexts = coll.to_contexts() assert contexts[0].name == "inner_coll.my_task"
def sibling_subcollections_ignored(self): inner = Collection("inner", self.task) inner.configure({"foo": "hi there"}) inner2 = Collection("inner2", Task(_func, name="task2")) inner2.configure({"foo": "nope"}) root = Collection(inner, inner2) assert root.configuration("inner.task")["foo"] == "hi there" assert root.configuration("inner2.task2")["foo"] == "nope"
def invalid_subcollection_paths_result_in_KeyError(self): # Straight up invalid with raises(KeyError): Collection("meh").configuration("nope.task") # Exists but wrong level (should be 'root.task', not just # 'task') inner = Collection("inner", self.task) with raises(KeyError): Collection("root", inner).configuration("task")
def kwargs_act_as_name_args_for_given_objects(self): sub = Collection() @task def task1(c): pass ns = Collection(loltask=task1, notsub=sub) assert ns["loltask"] == task1 assert ns.collections["notsub"] == sub
def returns_unique_Collection_objects_for_same_input_module(self): # Ignoring self.c for now, just in case it changes later. # First, a module with no root NS mod = load("integration") c1 = Collection.from_module(mod) c2 = Collection.from_module(mod) assert c1 is not c2 # Now one *with* a root NS (which was previously buggy) mod2 = load("explicit_root") c3 = Collection.from_module(mod2) c4 = Collection.from_module(mod2) assert c3 is not c4
def keys_dont_have_to_exist_in_full_path(self): # Kinda duplicates earlier stuff; meh # Key only stored on leaf leaf = Collection("leaf", self.task) leaf.configure({"key": "leaf-value"}) middle = Collection("middle", leaf) root = Collection("root", middle) config = root.configuration("middle.leaf.task") assert config == {"key": "leaf-value"} # Key stored on mid + leaf but not root middle.configure({"key": "whoa"}) assert root.configuration("middle.leaf.task") == {"key": "whoa"}
def leading_and_trailing_underscores_are_not_affected(self): @task def _what_evers_(c): pass @task def _inner_cooler_(c): pass inner = Collection("inner", _inner_cooler_) contexts = Collection(_what_evers_, inner).to_contexts() expected = {"_what_evers_", "inner._inner_cooler_"} assert {x.name for x in contexts} == expected
def percolates_to_subcollection_tasks(self): @task def outer_task(c): pass @task def inner_task(c): pass coll = Collection(outer_task, inner=Collection(inner_task)) contexts = coll.to_contexts() expected = {"outer_task", "inner.inner_task"} assert {x.name for x in contexts} == expected
def _meh(self): @task def task1(c): pass @task def task2(c): pass @task def task3(c): pass submeh = Collection("submeh", task3) return Collection("meh", task1, task2, submeh)
def name_docstring_default_and_tasks(self): expected = dict( name="deploy", help="How to deploy our code and configs.", tasks=[ dict( name="db", help="Deploy to our database servers.", aliases=["db_servers"], ), dict( name="everywhere", help="Deploy to all targets.", aliases=[], ), dict( name="web", help="Update and bounce the webservers.", aliases=[], ), ], default="everywhere", collections=[], ) with support_path(): from tree import deploy coll = Collection.from_module(deploy) assert expected == coll.serialized()
def _nested_underscores(self, auto_dash_names=None): @task(aliases=["other_name"]) def my_task(c): pass @task(aliases=["other_inner"]) def inner_task(c): pass # NOTE: explicitly not giving kwarg to subcollection; this # tests that the top_level namespace performs the inverse # transformation when necessary. sub = Collection("inner_coll", inner_task) return Collection(my_task, sub, auto_dash_names=auto_dash_names)
def empty_named_collection(self): expected = dict(name="foo", help=None, tasks=[], default=None, collections=[]) assert expected == Collection("foo").serialized()
def aliases_are_dashed_too(self): @task(aliases=["hi_im_underscored"]) def whatever(c): pass contexts = Collection(whatever).to_contexts() assert "hi_im_underscored" in contexts[0].aliases
def context_names_automatically_become_dashed(self): @task def my_task(c): pass contexts = Collection(my_task).to_contexts() assert contexts[0].name == "my_task"
def honors_subcollection_default_tasks_on_subcollection_name(self): sub = Collection.from_module(load("decorators")) self.c.add_collection(sub) # Sanity assert self.c["decorators.biz"] is sub["biz"] # Real test assert self.c["decorators"] is self.c["decorators.biz"]
def submodule_names_are_stripped_to_last_chunk(self): with support_path(): from package import module c = Collection.from_module(module) assert module.__name__ == "package.module" assert c.name == "module" assert "mytask" in c # Sanity
def name_docstring_default_tasks_and_collections(self): docs = dict( name="docs", help="Tasks for managing Sphinx docs.", tasks=[ dict(name="all", help="Build all doc formats.", aliases=[]), dict(name="html", help="Build HTML output only.", aliases=[]), dict(name="pdf", help="Build PDF output only.", aliases=[]), ], default="all", collections=[], ) python = dict( name="python", help="PyPI/etc distribution artifacts.", tasks=[ dict( name="all", help="Build all Python packages.", aliases=[], ), dict( name="sdist", help="Build classic style tar.gz.", aliases=[], ), dict(name="wheel", help="Build a wheel.", aliases=[]), ], default="all", collections=[], ) expected = dict( name="build", help="Tasks for compiling static code and assets.", tasks=[ dict( name="all", help="Build all necessary artifacts.", aliases=["everything"], ), dict( name="c_ext", help="Build our internal C extension.", aliases=["ext"], ), dict(name="zap", help="A silly way to clean.", aliases=[]), ], default="all", collections=[docs, python], ) with support_path(): from tree import build coll = Collection.from_module(build) assert expected == coll.serialized()
def invalid_path(self): # This is really just testing Lexicon/dict behavior but w/e, good # to be explicit, esp if we ever want this to become Exit or # another custom exception. (For now most/all callers manually # catch KeyError and raise Exit just to keep most Exit use high up # in the stack...) with raises(KeyError): collection = Collection.from_module(load("tree")) collection.subcollection_from_path("lol.whatever.man")
def parents_overwrite_children_in_path(self): inner = Collection("inner", self.task) inner.configure({"foo": "inner"}) self.root.add_collection(inner) # Before updating root collection's config, reflects inner assert self.root.configuration("inner.task")["foo"] == "inner" self.root.configure({"foo": "outer"}) # After, reflects outer (since that now overrides) assert self.root.configuration("inner.task")["foo"] == "outer"
def initial_string_arg_meshes_with_varargs_and_kwargs(self): @task def task1(c): pass @task def task2(c): pass sub = Collection("sub") ns = Collection("root", task1, sub, sometask=task2) for x, y in ( (ns.name, "root"), (ns["task1"], task1), (ns.collections["sub"], sub), (ns["sometask"], task2), ): assert x == y
def positional_arglist_preserves_order_given(self): @task(positional=("second", "first")) def mytask(c, first, second, third): pass coll = Collection() coll.add_task(mytask) c = coll.to_contexts()[0] expected = [c.args["second"], c.args["first"]] assert c.positional_args == expected
def access_merges_from_subcollections(self): inner = Collection("inner", self.task) inner.configure({"foo": "bar"}) self.root.configure({"biz": "baz"}) # With no inner collection assert set(self.root.configuration().keys()) == {"biz"} # With inner collection self.root.add_collection(inner) keys = set(self.root.configuration("inner.task").keys()) assert keys == {"foo", "biz"}
def returns_list_of_help_tuples(self): # Walks own list of flags/args, ensures resulting map to help_for() # TODO: consider redoing help_for to be more flexible on input -- # arg value or flag; or even Argument objects. ? @task(help={"otherarg": "other help"}) def mytask(c, myarg, otherarg): pass c = Collection(mytask).to_contexts()[0] expected = [c.help_for("--myarg"), c.help_for("--otherarg")] assert c.help_tuples() == expected
def setup(self): @task def mytask(c, text, boolean=False, number=5): print(text) @task(aliases=["mytask27"]) def mytask2(c): pass @task(aliases=["othertask"], default=True) def subtask(c): pass sub = Collection("sub", subtask) self.c = Collection(mytask, mytask2, sub) self.contexts = self.c.to_contexts() alias_tups = [list(x.aliases) for x in self.contexts] self.aliases = reduce(operator.add, alias_tups, []) # Focus on 'mytask' as it has the more interesting sig self.context = [x for x in self.contexts if x.name == "mytask"][0]
def empty_named_docstringed_collection(self): expected = dict( name="foo", help="Hi doc", tasks=[], default=None, collections=[], ) coll = Collection("foo") coll.__doc__ = "Hi doc" assert expected == coll.serialized()
def setup(self): mod = load("explicit_root") mod.ns.configure({ "key": "builtin", "otherkey": "yup", "subconfig": { "mykey": "myvalue" }, }) mod.ns.name = "builtin_name" self.unchanged = Collection.from_module(mod) self.changed = Collection.from_module( mod, name="override_name", config={ "key": "override", "subconfig": { "myotherkey": "myothervalue" }, }, )
def setup(self): # Normal, non-task/collection related Context self.vanilla = Context( args=(Argument("foo"), Argument("bar", help="bar the baz")) ) # Task/Collection generated Context # (will expose flags n such) @task(help={"otherarg": "other help"}, optional=["optval"]) def mytask(c, myarg, otherarg, optval, intval=5): pass col = Collection(mytask) self.tasked = col.to_contexts()[0]