Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[run]
branch = False
source = datajoint

[report]
1 change: 1 addition & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
repo_token: fd0BoXG46TPReEem0uMy7BJO5j0w1MQiY
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ install:
# command to run tests
script:
- nosetests -vv --with-coverage --cover-package=datajoint

after_success:
- coveralls
2 changes: 1 addition & 1 deletion datajoint/base_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def delete(self):
restrictions = defaultdict(list)
if self.restrictions:
restrict_by_me.add(self.full_table_name)
restrictions[self.full_table_name] = self.restrictions # copy own restrictions
restrictions[self.full_table_name].append(self.restrictions) # copy own restrictions
for r in relations.values():
restrict_by_me.update(r.references)
for name, r in relations.items():
Expand Down
22 changes: 8 additions & 14 deletions datajoint/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from collections.abc import Callable, Iterable
from functools import wraps
import warnings

from .blob import unpack
import numpy as np
from datajoint import DataJointError
Expand Down Expand Up @@ -39,6 +40,7 @@ def copy_first(f):
"""
Decorates methods that return an altered copy of self
"""

@wraps(f)
def ret(*args, **kwargs):
args = list(args)
Expand Down Expand Up @@ -148,7 +150,7 @@ def __call__(self, **kwargs):
if behavior['limit'] is None and behavior['offset'] is not None:
warnings.warn('Offset set, but no limit. Setting limit to a large number. '
'Consider setting a limit explicitly.')
behavior['limit'] = 2*len(self._relation)
behavior['limit'] = 2 * len(self._relation)
cur = self._relation.cursor(**behavior)

heading = self._relation.heading
Expand Down Expand Up @@ -217,19 +219,11 @@ def __getitem__(self, item):
return return_values[0] if single_output else return_values

def __repr__(self):
limit = config['display.limit']
width = config['display.width']
rel = self._relation.project(*self._relation.heading.non_blobs) # project out blobs
template = '%%-%d.%ds' % (width, width)
columns = rel.heading.names
repr_string = ' '.join([template % column for column in columns]) + '\n'
repr_string += ' '.join(['+' + '-' * (width - 2) + '+' for _ in columns]) + '\n'
for tup in rel.fetch(limit=limit):
repr_string += ' '.join([template % column for column in tup]) + '\n'
if len(rel) > limit:
repr_string += '...\n'
repr_string += ' (%d tuples)\n' % len(rel)
return repr_string
repr_str = """Fetch object for {items} items on {name}\n""".format(name=self._relation.__class__.__name__,
items=len(self._relation) )
repr_str += '\n'.join(
["\t{key}:\t{value}".format(key=k, value=str(v)) for k, v in self.behavior.items() if v is not None])
return repr_str

def __len__(self):
return len(self._relation)
Expand Down
42 changes: 30 additions & 12 deletions datajoint/relational_operand.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,26 @@ def where_clause(self):
"""

def make_condition(arg, _negate=False):
if isinstance(arg, (str, AndList)):
return str(arg), _negate
if isinstance(arg, str):
return arg, _negate
elif isinstance(arg, AndList):
return '(' + ' AND '.join([make_condition(element)[0] for element in arg]) + ')', _negate

# semijoin or antijoin
if isinstance(arg, RelationalOperand):
elif isinstance(arg, RelationalOperand):
common_attributes = [q for q in self.heading.names if q in arg.heading.names]
if not common_attributes:
condition = 'FALSE' if negate else 'TRUE'
condition = 'FALSE' if _negate else 'TRUE'
else:
common_attributes = '`' + '`,`'.join(common_attributes) + '`'
condition = '({fields}) {not_}in ({subquery})'.format(
fields=common_attributes,
not_="not " if negate else "",
not_="not " if _negate else "",
subquery=arg.make_select(common_attributes))
return condition, False # negate is cleared
return condition, False # _negate is cleared

# mappings are turned into ANDed equality conditions
if isinstance(arg, Mapping):
elif isinstance(arg, Mapping):
condition = ['`%s`=%r' %
(k, v if not isinstance(v, (datetime.date, datetime.datetime, datetime.time)) else str(v))
for k, v in arg.items() if k in self.heading]
Expand Down Expand Up @@ -91,6 +93,8 @@ def make_condition(arg, _negate=False):
conditions.append(('NOT (%s)' if negate else '(%s)') % item)
return ' WHERE ' + ' AND '.join(conditions)

def __repr__(self):
return 'AND List: ' + repr(self._list)

class RelationalOperand(metaclass=abc.ABCMeta):
"""
Expand Down Expand Up @@ -244,9 +248,25 @@ def _repr_helper(self):
"""

def __repr__(self):
ret = self._repr_helper()
if self._restrictions:
ret += ' & %r' % self._restrictions
if config['loglevel'].lower() == 'debug':
ret = self._repr_helper()
if self._restrictions:
ret += ' & %r' % self._restrictions
else:
limit = config['display.limit']
width = config['display.width']
rel = self.project(*self.heading.non_blobs) # project out blobs
template = '%%-%d.%ds' % (width, width)
columns = rel.heading.names
repr_string = ' '.join([template % column for column in columns]) + '\n'
repr_string += ' '.join(['+' + '-' * (width - 2) + '+' for _ in columns]) + '\n'
for tup in rel.fetch(limit=limit):
repr_string += ' '.join([template % column for column in tup]) + '\n'
if len(rel) > limit:
repr_string += '...\n'
repr_string += ' (%d tuples)\n' % len(rel)
return repr_string

return ret

def _repr_html_(self):
Expand All @@ -259,8 +279,6 @@ def _repr_html_(self):
['\n'.join(['<td>%s</td>' % column for column in tup]) for tup in rel.fetch(limit=limit)]),
tuples=len(rel)
)


return """<div style="max-height:1000px;max-width:1500px;overflow:auto;">\n
<table border="1" class="dataframe">\n
<thead>\n
Expand Down
2 changes: 1 addition & 1 deletion datajoint/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
#
'connection.init_function': None,
#
'loglevel': 'DEBUG',
'loglevel': 'INFO',
#
'safemode': True,
#
Expand Down
6 changes: 3 additions & 3 deletions tests/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import random

import numpy as np
import datajoint as dj
from . import PREFIX, CONN_INFO
Expand Down Expand Up @@ -55,7 +56,6 @@ def _prepare(self):

@schema
class Language(dj.Lookup):

definition = """
# languages spoken by some of the developers

Expand Down Expand Up @@ -99,7 +99,7 @@ def _make_tuples(self, key):
self.insert1(
dict(key,
experiment_id=experiment_id,
experiment_date=(date.today()-timedelta(random.expovariate(1/30))).isoformat(),
experiment_date=(date.today() - timedelta(random.expovariate(1 / 30))).isoformat(),
username=random.choice(users)))


Expand All @@ -121,7 +121,7 @@ def _make_tuples(self, key):
self.insert1(
dict(key,
trial_id=trial_id,
start_time=random.random()*1e9
start_time=random.random() * 1e9
))


Expand Down
27 changes: 27 additions & 0 deletions tests/schema_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import datajoint as dj
from . import PREFIX, CONN_INFO


schema = dj.schema(PREFIX + '_relational', locals(), connection=dj.conn(**CONN_INFO))


Expand Down Expand Up @@ -102,3 +103,29 @@ def _make_tuples(self, key):
for i, ref in enumerate(references):
if random.getrandbits(1):
sub.insert1(dict(key, id_f=i, **ref))


@schema
class DataA(dj.Lookup):
definition = """
idx : int
---
a : int
"""

@property
def contents(self):
yield from zip(range(5), range(5))


@schema
class DataB(dj.Lookup):
definition = """
idx : int
---
a : int
"""

@property
def contents(self):
yield from zip(range(5), range(5, 10))
10 changes: 9 additions & 1 deletion tests/test_relational_operand.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
assert_false, assert_true, assert_list_equal, \
assert_tuple_equal, assert_dict_equal, raises
import datajoint as dj
from .schema_simple import A, B, D, E, L
from .schema_simple import A, B, D, E, L, DataA, DataB
import datetime
from .schema import Experiment

Expand Down Expand Up @@ -182,3 +182,11 @@ def test_datetime():
e1 = Experiment() & dict(experiment_date=str(date))
e2 = Experiment() & dict(experiment_date=date)
assert_true(len(e1) == len(e2) > 0, 'Two date restriction do not yield the same result')

@staticmethod
def test_join_project_optimization():
"""Test optimization for join of projected relations with matching non-primary key"""
print(DataA().project() * DataB().project())
print(DataA())
assert_true(len(DataA().project() * DataB().project()) == len(DataA()) == len(DataB()),
"Join of projected relations does not work")