diff --git a/pep/_version.py b/pep/_version.py index 777f190d..8088f751 100644 --- a/pep/_version.py +++ b/pep/_version.py @@ -1 +1 @@ -__version__ = "0.8.0" +__version__ = "0.8.1" diff --git a/pep/models.py b/pep/models.py index 01d005e9..669be7ce 100644 --- a/pep/models.py +++ b/pep/models.py @@ -83,6 +83,7 @@ __all__ = __functions__ + __classes__ +MAX_PROJECT_SAMPLES_REPR = 12 ATTRDICT_METADATA = {"_force_nulls": False, "_attribute_identity": False} _LOGGER = logging.getLogger(__name__) @@ -200,9 +201,15 @@ def include_in_repr(attr, klazz): :return bool: whether to include attribute in an object's text representation """ + # TODO: try to leverage the class hierarchy to determine these exclusions. + ad_metadata = list(ATTRDICT_METADATA.keys()) + exclusions_by_class = { + AttributeDict.__name__: ad_metadata, + Project.__name__: ["_samples", "merge_table", "sheet", + "interfaces_by_protocol"] + ad_metadata, + Sample.__name__: ["sheet", "prj", "merged_cols"] + ad_metadata} classname = klazz.__name__ if isinstance(klazz, type) else klazz - return attr not in \ - {"Project": ["sheet", "interfaces_by_protocol"]}[classname] + return attr not in exclusions_by_class.get(classname, []) @@ -437,6 +444,13 @@ def __exit__(self, *args): +class IFilteredRepr(object): + def __repr__(self): + return + + + + @copy class AttributeDict(MutableMapping): """ @@ -608,7 +622,8 @@ def __len__(self): return sum(1 for _ in iter(self)) def __repr__(self): - return repr(self.__dict__) + return repr({k: v for k, v in self.__dict__.items() + if include_in_repr(k, klazz=self.__class__)}) def __str__(self): return "{}: {}".format(self.__class__.__name__, repr(self)) @@ -718,11 +733,11 @@ def __init__(self, config_file, subproject=None, else: _LOGGER.debug("Compute: %s", str(self.compute)) - # optional configs + # Optional behavioral parameters self.permissive = permissive self.file_checks = file_checks - # include the path to the config file + # Include the path to the config file. self.config_file = _os.path.abspath(config_file) # Parse config file @@ -790,11 +805,19 @@ def __init__(self, config_file, subproject=None, def __repr__(self): - """ Self-represent in the interpreter. """ - # First, parameterize the attribute filtration function by the class. - include = partial(include_in_repr, klazz=self.__class__) - # Then iterate over items, filtering what to include in representation. - return repr({k: v for k, v in self.__dict__.items() if include(k)}) + """ Representation in interpreter. """ + samples_message = "{} (from '{}')".\ + format(self.__class__.__name__, self.config_file) + try: + num_samples = len(self._samples) + except AttributeError: + pass + else: + samples_message += " with {} sample(s)".format(num_samples) + if num_samples <= MAX_PROJECT_SAMPLES_REPR: + samples_message += ": {}".format(repr(self._samples)) + meta_text = super(Project, self).__repr__() + return "{} -- {}".format(samples_message, meta_text) @property @@ -1446,8 +1469,7 @@ def parse_config_file(self, subproject=None): # Relative to environment config file. self.compute.submission_template = _os.path.join( _os.path.dirname(self.environment_file), - self.compute.submission_template - ) + self.compute.submission_template) # Required variables check if not hasattr(self.metadata, SAMPLE_ANNOTATIONS_KEY): @@ -1668,10 +1690,6 @@ def __ne__(self, other): return not self == other - def __repr__(self): - return "Sample '{}': {}".format(self.name, self.__dict__) - - def __str__(self): return "Sample '{}'".format(self.name) diff --git a/tests/models/test_models_smoke.py b/tests/models/test_models_smoke.py index acf9b6bf..4466d1e4 100644 --- a/tests/models/test_models_smoke.py +++ b/tests/models/test_models_smoke.py @@ -42,21 +42,6 @@ def test_Project_representations_smoke(self, proj, funcname): getattr(proj, funcname).__call__() - def test_project_repr_name_inclusion(self, proj, funcname): - """ Test Project text representation. """ - func = getattr(proj, funcname) - result = func.__call__() - assert type(result) is str - classname = proj.__class__.__name__ - if funcname == "__str__": - assert classname in result - elif funcname == "__repr__": - assert classname not in result - else: - raise ValueError("Unexpected representation function: {}". - format(funcname)) - - class ModelCreationSmokeTests: """ Smoketests for creation of various types of project-related models. """ @@ -72,29 +57,6 @@ def test_empty_project(self, path_empty_project): class ModelRepresentationSmokeTests: """ Tests for the text representation of important ADTs. """ - # NOTE: similar parameterization, but Project construction needs - # to be handled with greater care when testing the actual call. - - @pytest.mark.parametrize( - argnames="class_name", argvalues=pep.models.__classes__) - def test_implements_repr_smoke(self, class_name): - """ Each important ADT must implement a representation method. """ - - funcname = "__repr__" - - # Attempt a control assertion, that a subclass that doesn't override - # the given method of its superclass, uses the superclass version of - # the function in question. - class ObjectSubclass(object): - def __init__(self): - super(ObjectSubclass, self).__init__() - assert getattr(ObjectSubclass, funcname) is getattr(object, funcname) - - # Make the actual assertion of interest. - adt = getattr(pep.models, class_name) - assert getattr(adt, funcname) != \ - getattr(adt.__bases__[0], funcname) - @pytest.mark.parametrize( argnames="class_name",