Skip to content
Browse files

Declarative levels in auto istanciation

  • Loading branch information...
1 parent 4279a8c commit dd9fba002fe663bb8daffc28a0506f380cdf9979 @paradoxxxzero paradoxxxzero committed Apr 17, 2012
Showing with 188 additions and 84 deletions.
  1. +26 −26 pypet/__init__.py
  2. +1 −1 pypet/aggbuilder.py
  3. +48 −28 pypet/declarative.py
  4. +107 −23 pypet/test/test_declarative.py
  5. +6 −6 pypet/util.py
View
52 pypet/__init__.py
@@ -380,7 +380,7 @@ def __init__(self, level, id, label, filter=True, metadata=None):
self.id = id
self.label = label
self.filter = filter
- self.label_expr = cast(_literal_as_binds(self.label), types.Unicode)
+ self.label_expression = cast(_literal_as_binds(self.label), types.Unicode)
self.id_expr = _literal_as_binds(self.id)
self.metadata = metadata or MetaData()
@@ -399,7 +399,7 @@ def _as_selects(self, cuboid=None):
if isinstance(sub, IdSelect)]
assert len(subs) == 1
id_expr = subs[0]
- selects = [LabelSelect(self, column_clause=self.label_expr,
+ selects = [LabelSelect(self, column_clause=self.label_expression,
name='%s_label' % self._label_for_select, is_constant=True),
IdSelect(self, column_clause=self.id_expr,
name=self._label_for_select, is_constant=True)]
@@ -421,7 +421,7 @@ def children_query(self):
if self.level.child_level is None:
raise ValueError("Cannot build a query for a level without child")
query = self.level.child_level.members_query
- join_table_with_query(query, self.level.dim_column.table)
+ join_table_with_query(query, self.level.column.table)
query = query.where(self.level._id_column == self.id)
return query
@@ -448,14 +448,14 @@ def dimension(self):
class Level(CutPoint):
"""A level in a dimension hierarchy."""
- def __init__(self, name, dim_column=None, label_column=None,
- label_expr=lambda x: x,
+ def __init__(self, name, column=None, label_column=None,
+ label_expression=lambda x: x,
metadata=None):
self.label_column = (label_column if label_column is not None
- else dim_column)
+ else column)
self.name = name
- self.dim_column = dim_column
- self.label_expr = label_expr
+ self.column = column
+ self.label_expression = label_expression
self.child_level = None
self.parent_level = None
self.hierarchy = None
@@ -505,26 +505,26 @@ def _label_for_select(self):
@_generative
def replace_expr(self, expr, label_column=None):
- self.dim_column = expr
+ self.column = expr
self.child_level = None
if label_column is not None:
self.label_column = label_column
@_generative
- def replace_label_expr(self, label_expr):
- self.label_expr = label_expr
+ def replace_label_expression(self, label_expression):
+ self.label_expression = label_expression
@_generative
def replace_level(self, level):
self.child_level = level
@property
def _label_column(self):
- return self.label_expr(self.label_column)
+ return self.label_expression(self.label_column)
@property
def _id_column(self):
- return self.dim_column
+ return self.column
def _as_selects(self, cuboid=None):
sub_selects = []
@@ -537,9 +537,9 @@ def _as_selects(self, cuboid=None):
column_clause=self._label_column,
name='%s_label' % self._label_for_select,
dependencies=[],
- joins=sub_joins + [self.dim_column.table,
+ joins=sub_joins + [self.column.table,
self.label_column.table]),
- IdSelect(self, column_clause=self.dim_column,
+ IdSelect(self, column_clause=self.column,
name=self._label_for_select,
dependencies=[],
joins=sub_joins + [self._id_column.table,
@@ -549,8 +549,8 @@ def _adapt(self, aggregate):
for level in aggregate.levels:
if level.dimension == self.dimension:
if level.name == self.name:
- dim_column = aggregate.levels.get(level)
- return self.replace_expr(dim_column)
+ column = aggregate.levels.get(level)
+ return self.replace_expr(column)
else:
if self.child_level is None:
raise KeyError('Cannot find matching level in'
@@ -584,29 +584,29 @@ def members(self):
class ComputedLevel(Level):
- def __init__(self, name, dim_column=None, label_expr=None,
+ def __init__(self, name, column=None, label_expression=None,
function=lambda x: x, metadata=None):
- super(ComputedLevel, self).__init__(name, dim_column,
- label_expr=label_expr, metadata=None)
+ super(ComputedLevel, self).__init__(name, column,
+ label_expression=label_expression, metadata=None)
self.function = function
self.metadata = metadata or MetaData()
@_generative
def replace_level(self, level):
self.child_level = level
- self.dim_column = level.dim_column
+ self.column = level.column
@property
def _id_column(self):
- return self.function(self.dim_column).label(self.name)
+ return self.function(self.column).label(self.name)
@property
def _label_column(self):
- return self.label_expr(self._id_column)
+ return self.label_expression(self._id_column)
def _as_selects(self, cuboid=None):
col = self._id_column
- dep = IdSelect(self, column_clause=self.dim_column)
+ dep = IdSelect(self, column_clause=self.column)
return [IdSelect(self, name=self._label_for_select, column_clause=col,
dependencies=[dep]),
LabelSelect(self, name='%s_label' % self._label_for_select,
@@ -620,13 +620,13 @@ class _AllLevel(Level):
def __init__(self, name='All', label='All', metadata=None):
self.label = label
self.name = name
- self.label_expr = cast(_literal_as_binds(self.label), types.Unicode)
+ self.label_expression = cast(_literal_as_binds(self.label), types.Unicode)
self.parent_level = None
self.metadata = metadata or MetaData()
def _as_selects(self, cuboid=None):
return [LabelSelect(self, name=self._label_for_select,
- column_clause=self.label_expr, is_constant=True)]
+ column_clause=self.label_expression, is_constant=True)]
def _simplifiy(self, query):
return self
View
2 pypet/aggbuilder.py
@@ -396,7 +396,7 @@ def build(self, schema=None, with_trigger=False):
# DO NOT add foreign key for computed and all levels!
continue
fk = ForeignKeyConstraint(columns=[column.name],
- refcolumns=[axis.dim_column],
+ refcolumns=[axis.column],
table=table)
conn.execute(AddConstraint(fk))
axes = {axis: table.c[column.name] for axis, column in
View
76 pypet/declarative.py
@@ -2,6 +2,8 @@
from sqlalchemy import create_engine
from sqlalchemy.schema import MetaData
+_LEVEL_COUNTER = 0
+
def table(name_or_table, metadata):
if isinstance(name_or_table, basestring):
@@ -30,17 +32,38 @@ def __init__(self, *args, **kwargs):
self.kwargs = kwargs
+class MetaLevel(type):
+
+ def __new__(cls, classname, bases, classdict):
+ global _LEVEL_COUNTER
+ _LEVEL_COUNTER += 1
+
+ if bases == (Declarative,): # If Level do nothing
+ return type.__new__(cls, classname, bases, classdict)
+
+ level = pypet.Level(
+ '__unbound__',
+ classdict.get('column', None),
+ classdict.get('label_column', None),
+ classdict.get('label_expression', None))
+ level.definition = type.__new__(cls, classname, bases, classdict)
+ level._count = _LEVEL_COUNTER
+ return level
+
+
class Level(Declarative):
"""Declarative level with instance counter"""
- __level_counter = 0
+ __metaclass__ = MetaLevel
- def __init__(self, *args, **kwargs):
- self.__class__.__level_counter += 1
- self._count = self.__level_counter
- super(Level, self).__init__(*args, **kwargs)
+ def __new__(cls, *args, **kwargs):
+ global _LEVEL_COUNTER
+ _LEVEL_COUNTER += 1
- def __call__(self, name):
- return pypet.Level(name, *self.args, **self.kwargs)
+ args = tuple(['_unbound_'] + list(args))
+ level = pypet.Level(*args, **kwargs)
+ level.definition = cls
+ level._count = _LEVEL_COUNTER
+ return level
class Measure(Declarative):
@@ -58,25 +81,20 @@ def __call__(self, name, table):
class MetaHierarchy(type):
def __new__(cls, classname, bases, classdict):
- if bases == (Declarative,): # If Cube do nothing
- return type.__new__(cls, classname, bases, classdict)
+ hierarchy_class = type.__new__(cls, classname, bases, classdict)
+ if bases == (Declarative,): # If Hierarchy do nothing
+ return hierarchy_class
+
+ levels = []
+ for key in dir(hierarchy_class):
+ value = getattr(hierarchy_class, key)
+ if isinstance(value, pypet.Level):
+ value.name = key
+ levels.append(value)
- classdict['_declaratives'] = {}
- for base in bases:
- if hasattr(base, '_declaratives'):
- for key, value in base._declaratives.items():
- classdict['_declaratives'][key] = value
- classdict[key] = value
- levels = {}
- for key, value in classdict.items():
- if isinstance(value, Level):
- classdict['_declaratives'][key] = value
- levels[value._count] = value(key)
-
- levels = [level for _, level
- in sorted(levels.items(), key=lambda x: x[0])]
+ levels = sorted(levels, key=lambda x: x._count)
hierarchy = pypet.Hierarchy('_unbound_', levels)
- hierarchy.definition = type.__new__(cls, classname, bases, classdict)
+ hierarchy.definition = hierarchy_class
for level in levels:
if not hasattr(hierarchy, level.name):
setattr(hierarchy, level.name, level)
@@ -91,7 +109,7 @@ class Hierarchy(Declarative):
class MetaDimension(type):
def __new__(cls, classname, bases, classdict):
dimension_class = type.__new__(cls, classname, bases, classdict)
- if bases == (Declarative,): # If Cube do nothing
+ if bases == (Declarative,): # If Dimension do nothing
return dimension_class
hierarchies = []
@@ -101,8 +119,9 @@ def __new__(cls, classname, bases, classdict):
if isinstance(value, pypet.Hierarchy):
value.name = key
hierarchies.append(value)
- elif isinstance(value, Level):
- default_levels[value._count] = value(key)
+ elif isinstance(value, pypet.Level):
+ value.name = key
+ default_levels[value._count] = value
if len(default_levels):
levels = [level for _, level
@@ -114,7 +133,7 @@ def __new__(cls, classname, bases, classdict):
hierarchies.append(default_hierarchy)
dimension = pypet.Dimension('_unbound_', hierarchies)
- dimension.definition = dimension_class
+ dimension.definition = type.__new__(cls, classname, bases, classdict)
for hierarchy in hierarchies:
if not hasattr(dimension, hierarchy.name):
setattr(dimension, hierarchy.name, hierarchy)
@@ -161,6 +180,7 @@ def __new__(cls, classname, bases, classdict):
aggregates=classdict.get('__aggregates__', None),
fact_count_column=column(
classdict.get('__fact_count_column__', None), fact_table))
+ cube.definition = cube_class
for thing in dimensions + measures:
if not hasattr(cube, thing.name):
View
130 pypet/test/test_declarative.py
@@ -20,30 +20,14 @@ class TimeHierarchy(Hierarchy):
l2 = Level()
l3 = Level()
- class SubTimeHierarchy(TimeHierarchy.definition):
- l2 = Level()
-
- class SubSubTimeHierarchy(SubTimeHierarchy.definition):
- l3 = Level()
- l4 = Level()
-
assert isinstance(TimeHierarchy, pypet.Hierarchy)
assert TimeHierarchy.levels.keys() == ['All', 'l1', 'l2', 'l3']
- assert SubTimeHierarchy.levels.keys() == ['All', 'l1', 'l2', 'l3']
- assert hasattr(SubSubTimeHierarchy, 'l4')
- assert SubSubTimeHierarchy.levels.keys() == ['All', 'l1', 'l2', 'l3', 'l4']
for key in ('l1', 'l2', 'l3'):
assert isinstance(getattr(TimeHierarchy, key), pypet.Level)
- assert isinstance(getattr(SubTimeHierarchy, key), pypet.Level)
- assert isinstance(getattr(SubSubTimeHierarchy, key), pypet.Level)
assert getattr(TimeHierarchy, key).name == key
assert getattr(TimeHierarchy, key) == getattr(TimeHierarchy, key)
- assert getattr(TimeHierarchy, key) != getattr(SubTimeHierarchy, key)
- assert getattr(TimeHierarchy, key) != getattr(SubSubTimeHierarchy, key)
- assert getattr(SubTimeHierarchy, key) != getattr(
- SubSubTimeHierarchy, key)
def test_dimension():
@@ -53,7 +37,7 @@ class TimeHierarchy(Hierarchy):
l2 = Level()
l3 = Level()
- class TimeHierarchy2(TimeHierarchy.definition):
+ class TimeHierarchy2(Hierarchy):
l1_2 = Level()
l2_2 = Level()
l3_2 = Level()
@@ -71,14 +55,15 @@ class SpaceDimension(Dimension):
assert isinstance(TimeDimension.h1, pypet.Hierarchy)
assert isinstance(TimeDimension.h1.l1, pypet.Level)
assert TimeDimension.h1.l1 == TimeDimension.h1.l1
- assert TimeDimension.h1.l1 != TimeDimension.h2.l1
+ assert TimeDimension.h1.l1 != TimeDimension.h2.l1_2
+ assert TimeDimension.h1.name == 'h1'
+ assert TimeDimension.h1.l1.name == 'l1'
assert TimeDimension.h1.levels.keys() == ['All', 'l1', 'l2', 'l3']
- assert TimeDimension.h2.levels.keys() == ['All', 'l1', 'l2', 'l3',
- 'l1_2', 'l2_2', 'l3_2']
+ assert TimeDimension.h2.levels.keys() == ['All', 'l1_2', 'l2_2', 'l3_2']
assert len(TimeDimension.hierarchies) == 2
assert len([level for h in TimeDimension.hierarchies.values()
- for level in h.levels]) == 11
+ for level in h.levels]) == 8
assert TimeDimension.h1 != SpaceDimension.h1
assert TimeDimension.h1.l1 != SpaceDimension.h1.l1
@@ -116,12 +101,15 @@ class StoreDimension(Dimension):
country = Level(c('country.country_id'), c('country.country_name'))
store = Level(c('store.store_id'), c('store.store_name'))
- class ProductDimension(Dimension):
+ class ProductHierarchy(Hierarchy):
category = Level(c('product_category.product_category_id'),
c('product_category.product_category_name'))
product = Level(c('product.product_id'),
c('product.product_name'))
+ class ProductDimension(Dimension):
+ default = ProductHierarchy
+
# class TimeDimension(Dimension):
# year = Level()
# month = Level()
@@ -146,4 +134,100 @@ class TestCube(Cube):
# assert isinstance(TestCube.time.default, pypet.Hierarchy)
# assert isinstance(TestCube.time.default.day, pypet.Level)
assert isinstance(TestCube.query, pypet.Query)
- TestCube.query.axis(StoreDimension.default.region).execute()
+ assert len(
+ TestCube.query.axis(StoreDimension.default.region).execute()) == 2
+
+ def test_cube_nested(self):
+ def c(col):
+ table, column = col.split('.')
+ return self.metadata.tables[table].columns[column]
+
+ class TestCube(Cube):
+ __metadata__ = self.metadata
+ __fact_table__ = 'facts_table'
+ __fact_count_column__ = 'qty'
+
+ class product(Dimension):
+ class default(Hierarchy):
+ class category(Level):
+ column = c('product_category.product_category_id')
+ label_column = c(
+ 'product_category.product_category_name')
+
+ def label_expression(self, col):
+ return col + ' %'
+
+ class product(Level):
+ column = c('product.product_id')
+ label_column = c('product.product_name')
+
+ class store(Dimension):
+ region = Level(
+ c('region.region_id'),
+ c('region.region_name'))
+ country = Level(
+ c('country.country_id'),
+ c('country.country_name'))
+ store = Level(
+ c('store.store_id'),
+ c('store.store_name'))
+
+ time = self.time_dim
+
+ price = Measure()
+ quantity = Measure('qty', agg=aggregates.sum)
+
+ assert isinstance(TestCube, pypet.Cube)
+ assert isinstance(TestCube.price, pypet.Measure)
+ assert isinstance(TestCube.quantity, pypet.Measure)
+ assert isinstance(TestCube.time, pypet.Dimension)
+ assert isinstance(TestCube.product, pypet.Dimension)
+ assert isinstance(TestCube.product.default, pypet.Hierarchy)
+ assert isinstance(TestCube.product.default.category, pypet.Level)
+ assert TestCube.product.default.category.name == 'category'
+ assert TestCube.product.default.category.label_expression(None,
+ '42') == '42 %'
+ assert TestCube.product.default.category.column == c(
+ 'product_category.product_category_id')
+ assert TestCube.product.default.category.label_column == c(
+ 'product_category.product_category_name')
+ assert isinstance(TestCube.store.default, pypet.Hierarchy)
+ # assert isinstance(TestCube.time.default, pypet.Hierarchy)
+ # assert isinstance(TestCube.time.default.day, pypet.Level)
+ assert isinstance(TestCube.query, pypet.Query)
+ assert len(
+ TestCube.query.axis(TestCube.store.default.region).execute()) == 2
+
+
+def fail_test_inheritence_hierarchy():
+
+ class TimeHierarchy(Hierarchy):
+ l1 = Level()
+ l2 = Level()
+ l3 = Level()
+
+ class SubTimeHierarchy(TimeHierarchy.definition):
+ l2 = Level()
+
+ class SubSubTimeHierarchy(SubTimeHierarchy.definition):
+ l3 = Level()
+ l4 = Level()
+
+ assert isinstance(TimeHierarchy, pypet.Hierarchy)
+ assert TimeHierarchy.levels.keys() == ['All', 'l1', 'l2', 'l3']
+ assert SubTimeHierarchy.levels.keys() == ['All', 'l1', 'l2', 'l3']
+ assert hasattr(SubSubTimeHierarchy, 'l4')
+ assert SubSubTimeHierarchy.levels.keys() == ['All', 'l1', 'l2', 'l3', 'l4']
+
+ for key in ('l1', 'l2', 'l3'):
+ assert isinstance(getattr(TimeHierarchy, key), pypet.Level)
+ assert isinstance(getattr(SubTimeHierarchy, key), pypet.Level)
+ assert isinstance(getattr(SubSubTimeHierarchy, key), pypet.Level)
+
+ assert getattr(TimeHierarchy, key).name == key
+ assert getattr(TimeHierarchy, key) == getattr(TimeHierarchy, key)
+ assert getattr(TimeHierarchy, key) != getattr(SubTimeHierarchy, key)
+ assert getattr(TimeHierarchy, key) != getattr(SubSubTimeHierarchy, key)
+ assert getattr(SubTimeHierarchy, key) != getattr(
+ SubSubTimeHierarchy, key)
+
View
12 pypet/util.py
@@ -12,7 +12,7 @@
class TimeLevel(ComputedLevel):
- def __init__(self, name, dim_column, time_slice=None):
+ def __init__(self, name, column, time_slice=None):
if time_slice is None:
time_slice = name
@@ -21,14 +21,14 @@ def partial_trunc(column):
def partial_extract(column):
return extract(time_slice, column)
- label_expr = FORMAT_FUNCTIONS.get(time_slice, partial_extract)
- super(TimeLevel, self).__init__(name, dim_column,
- function=partial_trunc, label_expr=label_expr)
+ label_expression = FORMAT_FUNCTIONS.get(time_slice, partial_extract)
+ super(TimeLevel, self).__init__(name, column,
+ function=partial_trunc, label_expression=label_expression)
class TimeDimension(Dimension):
- def __init__(self, name, dim_column, time_levels):
- levels = [TimeLevel(level, dim_column) for level in time_levels]
+ def __init__(self, name, column, time_levels):
+ levels = [TimeLevel(level, column) for level in time_levels]
super(TimeDimension, self).__init__(name, [Hierarchy('default',
levels)])

0 comments on commit dd9fba0

Please sign in to comment.
Something went wrong with that request. Please try again.