Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to Container classes #1123

Merged
merged 27 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
488fc55
change prefix and members of leakage and concentration containers
kosack Aug 22, 2019
7f58494
added ImageParametersContainer to group various parameter containers
kosack Aug 22, 2019
47bfeb6
fixed typo
kosack Aug 22, 2019
467244c
added EventIndexContainer and TelEventIndexContainers
kosack Aug 22, 2019
6cafa86
added telescope type_id to TelIndexContainer
kosack Aug 23, 2019
1fe53b8
rename leakage parameters
kosack Aug 28, 2019
961486c
Merge branch 'master' of https://github.com/cta-observatory/ctapipe i…
kosack Aug 30, 2019
ee73032
new containers, renaming, and deprecation
kosack Sep 30, 2019
8a6cf8d
Update SimTelEventSource
kosack Sep 30, 2019
d8f98e5
add "Set" to standard traits
kosack Sep 30, 2019
4d530d0
fix forgotten rename in test
kosack Sep 30, 2019
a4e2b5b
fix test using leakage parameters (renamed)
kosack Sep 30, 2019
7f580d6
fix tutorial
kosack Sep 30, 2019
b6b39ed
fix a few style warnings
kosack Sep 30, 2019
a10a881
change percent to fraction in LeakageContainer
kosack Oct 2, 2019
cef2b6a
also fix test
kosack Oct 2, 2019
d1a7811
updated names of leakage parameters again
kosack Oct 14, 2019
1aafefc
update leakage names in calculation
kosack Oct 14, 2019
ad79916
again updated leakage names
kosack Oct 14, 2019
570301f
fixed one more place I forgot to rename
kosack Oct 15, 2019
92816b6
Merge branch 'master' of https://github.com/cta-observatory/ctapipe i…
kosack Oct 23, 2019
541699b
used @maxnoe's suggestion of a Deprecated wrapper class
kosack Oct 23, 2019
ea6f8a6
help -> reason (to avoid keyword)
kosack Oct 23, 2019
7e37739
removed accidental commit
kosack Oct 23, 2019
ab4350f
Revert "used @maxnoe's suggestion of a Deprecated wrapper class"
kosack Oct 23, 2019
6f6b7f5
add reason for deprecation and issue warning
kosack Oct 23, 2019
c8e7313
fix test
kosack Oct 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ctapipe/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from .component import Component, non_abstract_children
from .container import Container, Field, Map
from .container import Container, Field, DeprecatedField, Map
from .provenance import Provenance, get_module_version
from .tool import Tool, ToolConfigurationError

Expand All @@ -13,6 +13,7 @@
'Container',
'Tool',
'Field',
'DeprecatedField',
'Map',
'Provenance',
'ToolConfigurationError',
Expand Down
71 changes: 40 additions & 31 deletions ctapipe/core/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from copy import deepcopy
from pprint import pformat
from textwrap import wrap
import warnings


class Field:
Expand All @@ -28,12 +29,22 @@ def __init__(self, default, description="", unit=None, ucd=None):
self.ucd = ucd

def __repr__(self):
desc = f'{self.description}'
desc = f"{self.description}"
if self.unit is not None:
desc += f' [{self.unit}]'
desc += f" [{self.unit}]"
return desc


class DeprecatedField(Field):
kosack marked this conversation as resolved.
Show resolved Hide resolved
""" used to mark which fields may be removed in next version """
def __init__(self, default, description="", unit=None, ucd=None, reason=""):
super().__init__(default=default, description=description, unit=unit, ucd=ucd)
warnings.warn(f"Field {self} is deprecated. {reason}", DeprecationWarning)
self.reason = reason




class ContainerMeta(type):
"""
The MetaClass for the Containers
Expand All @@ -47,27 +58,24 @@ class ContainerMeta(type):
"""

def __new__(cls, name, bases, dct):
field_names = [
k for k, v in dct.items()
if isinstance(v, Field)
]
dct['__slots__'] = tuple(field_names + ['meta', 'prefix'])
dct['fields'] = {}
field_names = [k for k, v in dct.items() if isinstance(v, Field)]
dct["__slots__"] = tuple(field_names + ["meta", "prefix"])
dct["fields"] = {}

# inherit fields from baseclasses
for b in bases:
if issubclass(b, Container):
for k, v in b.fields.items():
dct['fields'][k] = v
dct["fields"][k] = v

for k in field_names:
dct['fields'][k] = dct.pop(k)
dct["fields"][k] = dct.pop(k)

new_cls = type.__new__(cls, name, bases, dct)

# if prefix was not set as a class variable, build a default one
if 'container_prefix' not in dct:
new_cls.container_prefix = name.lower().replace('container', '')
if "container_prefix" not in dct:
new_cls.container_prefix = name.lower().replace("container", "")

return new_cls

Expand Down Expand Up @@ -142,10 +150,10 @@ def __setitem__(self, key, value):

def items(self, add_prefix=False):
"""Generator over (key, value) pairs for the items"""
if not add_prefix or self.prefix == '':
if not add_prefix or self.prefix == "":
return ((k, getattr(self, k)) for k in self.fields.keys())

return ((self.prefix + '_' + k, getattr(self, k)) for k in self.fields.keys())
return ((self.prefix + "_" + k, getattr(self, k)) for k in self.fields.keys())

def keys(self):
"""Get the keys of the container"""
Expand Down Expand Up @@ -176,13 +184,14 @@ def as_dict(self, recursive=False, flatten=False, add_prefix=False):
for key, val in self.items(add_prefix=add_prefix):
if isinstance(val, Container) or isinstance(val, Map):
if flatten:
d.update({
f"{key}_{k}": v
for k, v in val.as_dict(
recursive,
add_prefix=add_prefix
).items()
})
d.update(
{
f"{key}_{k}": v
for k, v in val.as_dict(
recursive, add_prefix=add_prefix
).items()
}
)
else:
d[key] = val.as_dict(
recursive=recursive, flatten=flatten, add_prefix=add_prefix
Expand Down Expand Up @@ -220,7 +229,7 @@ def __repr__(self):
if isinstance(getattr(self, name), Map):
extra = "[*]"
desc = "{:>30s}: {}".format(name + extra, repr(item))
lines = wrap(desc, 80, subsequent_indent=' ' * 32)
lines = wrap(desc, 80, subsequent_indent=" " * 32)
text.extend(lines)
return "\n".join(text)

Expand All @@ -239,17 +248,17 @@ def as_dict(self, recursive=False, flatten=False, add_prefix=False):
for key, val in self.items():
if isinstance(val, Container) or isinstance(val, Map):
if flatten:
d.update({
f"{key}_{k}": v
for k, v in val.as_dict(
recursive, add_prefix=add_prefix
).items()
})
d.update(
{
f"{key}_{k}": v
for k, v in val.as_dict(
recursive, add_prefix=add_prefix
).items()
}
)
else:
d[key] = val.as_dict(
recursive=recursive,
flatten=flatten,
add_prefix=add_prefix,
recursive=recursive, flatten=flatten, add_prefix=add_prefix
)
continue
d[key] = val
Expand Down
65 changes: 33 additions & 32 deletions ctapipe/core/tests/test_container.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,51 @@
# Licensed under a 3-clause BSD style license - see LICENSE.rst
import pytest
from ctapipe.core import Container, Field, Map
from ctapipe.core import Container, Field, Map, DeprecatedField


def test_prefix():
class AwesomeContainer(Container):
pass

# make sure the default prefix is class name without container
assert AwesomeContainer.container_prefix == 'awesome'
assert AwesomeContainer().prefix == 'awesome'
assert AwesomeContainer.container_prefix == "awesome"
assert AwesomeContainer().prefix == "awesome"

# make sure we can set the class level prefix at definition time
class ReallyAwesomeContainer(Container):
container_prefix = 'test'
container_prefix = "test"

assert ReallyAwesomeContainer.container_prefix == 'test'
assert ReallyAwesomeContainer.container_prefix == "test"
r = ReallyAwesomeContainer()
assert r.prefix == 'test'
assert r.prefix == "test"

ReallyAwesomeContainer.container_prefix = 'test2'
ReallyAwesomeContainer.container_prefix = "test2"
# new instance should have the old prefix, old instance
# the one it was created with
assert ReallyAwesomeContainer().prefix == 'test2'
assert r.prefix == 'test'
assert ReallyAwesomeContainer().prefix == "test2"
assert r.prefix == "test"

# Make sure we can set the class level prefix at runtime
ReallyAwesomeContainer.container_prefix = 'foo'
assert ReallyAwesomeContainer().prefix == 'foo'
ReallyAwesomeContainer.container_prefix = "foo"
assert ReallyAwesomeContainer().prefix == "foo"

# make sure we can assign instance level prefixes
c1 = ReallyAwesomeContainer()
c2 = ReallyAwesomeContainer()
c2.prefix = 'c2'
c2.prefix = "c2"

assert c1.prefix == 'foo'
assert c2.prefix == 'c2'
assert c1.prefix == "foo"
assert c2.prefix == "c2"


def test_inheritance():

class ExampleContainer(Container):
a = Field(None)

class SubclassContainer(ExampleContainer):
b = Field(None)

assert 'a' in SubclassContainer.fields
assert "a" in SubclassContainer.fields

c = SubclassContainer()
assert c.a is None
Expand All @@ -60,7 +59,6 @@ class SubclassContainer(ExampleContainer):


def test_multiple_inheritance():

class ContainerA(Container):
a = Field(None)

Expand All @@ -70,12 +68,11 @@ class ContainerB(ContainerA):
class ContainerC(ContainerB):
c = Field(None)

assert 'a' in ContainerC.fields
assert 'b' in ContainerC.fields
assert "a" in ContainerC.fields
assert "b" in ContainerC.fields


def test_override_inheritance():

class ContainerA(Container):
a = Field(1)

Expand All @@ -90,7 +87,6 @@ class ContainerB(ContainerA):


def test_container():

class ExampleContainer(Container):
x = Field(-1, "x value")
y = Field(-1, "y value")
Expand All @@ -117,8 +113,8 @@ class ExampleContainer(Container):
assert cont.x == -1

# test adding metadata
cont.meta['stuff'] = 'things'
assert 'stuff' in cont.meta and cont.meta['stuff'] == 'things'
cont.meta["stuff"] = "things"
assert "stuff" in cont.meta and cont.meta["stuff"] == "things"


def test_child_containers():
Expand All @@ -134,7 +130,6 @@ class ParentContainer(Container):


def test_map_containers():

class ChildContainer(Container):
z = Field(1, "sub-item")

Expand All @@ -154,7 +149,6 @@ class ParentContainer(Container):


def test_container_as_dict():

class ChildContainer(Container):
z = Field(1, "sub-item")

Expand All @@ -167,18 +161,25 @@ class ParentContainer(Container):
the_flat_dict = cont.as_dict(recursive=True, flatten=True)
the_dict = cont.as_dict(recursive=True, flatten=False)

assert 'child_z' in the_flat_dict
assert 'child' in the_dict and 'z' in the_dict['child']
assert "child_z" in the_flat_dict
assert "child" in the_dict and "z" in the_dict["child"]


def test_container_brackets():

class TestContainer(Container):
class ExampleContainer(Container):
answer = Field(-1, "The answer to all questions")

t = TestContainer()
t = ExampleContainer()

t['answer'] = 42
t["answer"] = 42

with pytest.raises(AttributeError):
t['foo'] = 5
t["foo"] = 5

def test_deprecated_field():
class ExampleContainer(Container):
answer = DeprecatedField(-1, "The answer to all questions", reason="because")

cont = ExampleContainer()
cont.answer = 6

29 changes: 16 additions & 13 deletions ctapipe/core/traits.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from traitlets import (
Bool,
CaselessStrEnum,
CRegExp,
Dict,
Enum,
Float,
Expand All @@ -16,6 +15,8 @@
TraitType,
Unicode,
observe,
Set,
CRegExp,
)
from traitlets.config import boolean_flag as flag

Expand All @@ -31,6 +32,7 @@
"Long",
"List",
"Bool",
"Set",
"CRegExp",
"Dict",
"flag",
Expand All @@ -52,20 +54,21 @@


class Path(TraitType):
def __init__(self, exists=None, directory_ok=True, file_ok=True):
"""
A path Trait for input/output files.
"""
A path Trait for input/output files.

Parameters
----------
exists: boolean or None
If True, path must exist, if False path must not exist
Parameters
----------
exists: boolean or None
If True, path must exist, if False path must not exist

directory_ok: boolean
If False, path must not be a directory
file_ok: boolean
If False, path must not be a file
"""
directory_ok: boolean
If False, path must not be a directory
file_ok: boolean
If False, path must not be a file
"""

def __init__(self, exists=None, directory_ok=True, file_ok=True):
super().__init__()
self.exists = exists
self.directory_ok = directory_ok
Expand Down
Loading