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
16 changes: 7 additions & 9 deletions datajoint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@
__author__ = "Dimitri Yatsenko, Edgar Walker, and Fabian Sinz at Baylor College of Medicine"
__version__ = "0.2"
__all__ = ['__author__', '__version__',
'config',
'Connection', 'Heading', 'Relation', 'FreeRelation', 'Not',
'Relation', 'schema',
'Manual', 'Lookup', 'Imported', 'Computed', 'Part',
'conn', 'kill']
'config', 'conn', 'kill',
'Connection', 'Heading', 'Relation', 'FreeRelation', 'Not', 'schema',
'Manual', 'Lookup', 'Imported', 'Computed', 'Part']


# define an object that identifies the primary key in RelationalOperand.__getitem__
class PrimaryKey:
class key:
"""
object that allows requesting the primary key in Fetch.__getitem__
"""
pass

key = PrimaryKey


class DataJointError(Exception):
"""
Expand Down
21 changes: 18 additions & 3 deletions datajoint/autopopulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import abc
import logging
import datetime
import random
from .relational_operand import RelationalOperand
from . import DataJointError
from .relation import FreeRelation
Expand Down Expand Up @@ -52,7 +53,8 @@ def target(self):
"""
return self

def populate(self, restriction=None, suppress_errors=False, reserve_jobs=False):
def populate(self, restriction=None, suppress_errors=False,
reserve_jobs=False, order="original"):
"""
rel.populate() calls rel._make_tuples(key) for every primary key in self.populated_from
for which there is not already a tuple in rel.
Expand All @@ -61,18 +63,31 @@ def populate(self, restriction=None, suppress_errors=False, reserve_jobs=False):
:param suppress_errors: suppresses error if true
:param reserve_jobs: currently not implemented
:param batch: batch size of a single job
:param order: "original"|"reverse"|"random" - the order of execution
"""
error_list = [] if suppress_errors else None
if not isinstance(self.populated_from, RelationalOperand):
raise DataJointError('Invalid populated_from value')

if self.connection.in_transaction:
raise DataJointError('Populate cannot be called during a transaction.')

valid_order = ['original', 'reverse', 'random']
if order not in valid_order:
raise DataJointError('The order argument must be one of %s' % str(valid_order))

error_list = [] if suppress_errors else None

jobs = self.connection.jobs[self.target.database]
table_name = self.target.table_name
unpopulated = (self.populated_from & restriction) - self.target.project()
for key in unpopulated.fetch.keys():
keys = unpopulated.fetch.keys()
if order == "reverse":
keys = list(keys).reverse()
elif order == "random":
keys = list(keys)
random.shuffle(keys)

for key in keys:
if not reserve_jobs or jobs.reserve(table_name, key):
self.connection.start_transaction()
if key in self.target: # already populated
Expand Down
83 changes: 74 additions & 9 deletions datajoint/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@


def prepare_attributes(relation, item):
"""
Used by fetch.__getitem__ to deal with slices

:param relation: the relation that created the fetch object
:param item: the item passed to __getitem__. Can be a string, a tuple, a list, or a slice.

:return: a tuple of items to fetch, a list of the corresponding attributes
:raise DataJointError: if item does not match one of the datatypes above
"""
if isinstance(item, str) or item is PRIMARY_KEY:
item = (item,)
elif isinstance(item, int):
Expand All @@ -27,7 +36,7 @@ def prepare_attributes(relation, item):

def copy_first(f):
"""
decorates methods that return an altered copy of self
Decorates methods that return an altered copy of self
"""
@wraps(f)
def ret(*args, **kwargs):
Expand All @@ -39,6 +48,11 @@ def ret(*args, **kwargs):


class Fetch:
"""
A fetch object that handles retrieving elements from the database table.

:param relation: relation the fetch object retrieves data from.
"""

def __init__(self, relation):
if isinstance(relation, Fetch): # copy constructor
Expand All @@ -52,29 +66,70 @@ def __init__(self, relation):

@copy_first
def order_by(self, *args):
"""
Changes the state of the fetch object to order the results by a particular attribute.
The commands are handed down to mysql.

:param args: the attributes to sort by. If DESC is passed after the name, then the order is descending.
:return: a copy of the fetch object

Example:

>>> my_relation.fetch.order_by('language', 'name DESC')

"""
if len(args) > 0:
self.behavior['order_by'] = args
return self

@property
@copy_first
def as_dict(self):
"""
Changes the state of the fetch object to return dictionaries.

:return: a copy of the fetch object

Example:

>>> my_relation.fetch.as_dict()

"""
self.behavior['as_dict'] = True
return self

@copy_first
def limit(self, limit):
"""
Limits the number of items fetched.

:param limit: limit on the number of items
:return: a copy of the fetch object
"""
self.behavior['limit'] = limit
return self

@copy_first
def offset(self, offset):
"""
Offsets the number of itms fetched. Needs to be applied with limit.

:param offset: offset
:return: a copy of the fetch object
"""
if self.behavior['limit'] is None:
warnings.warn('You should supply a limit together with an offset,')
self.behavior['offset'] = offset
return self

@copy_first
def set_behavior(self, **kwargs):
"""
Sets the behavior like offset, limit, or order_by via keywords arguments.

:param kwargs: keyword arguments
:return: a copy of the fetch object
"""
self.behavior.update(kwargs)
return self

Expand Down Expand Up @@ -146,10 +201,11 @@ def __getitem__(self, item):
:return: tuple with an entry for each element of item

Examples:
a, b = relation['a', 'b']
a, b, key = relation['a', 'b', datajoint.key]
results = relation['a':'z'] # return attributes a-z as a tuple
results = relation[:-1] # return all but the last attribute

>>> a, b = relation['a', 'b']
>>> a, b, key = relation['a', 'b', datajoint.key]
>>> results = relation['a':'z'] # return attributes a-z as a tuple
>>> results = relation[:-1] # return all but the last attribute
"""
single_output = isinstance(item, str) or item is PRIMARY_KEY or isinstance(item, int)
item, attributes = prepare_attributes(self._relation, item)
Expand Down Expand Up @@ -185,13 +241,19 @@ def __len__(self):


class Fetch1:
"""
Fetch object for fetching exactly one row.

:param relation: relation the fetch object fetches data from
"""

def __init__(self, relation):
self._relation = relation

def __call__(self):
"""
This version of fetch is called when self is expected to contain exactly one tuple.

:return: the one tuple in the relation in the form of a dict
"""
heading = self._relation.heading
Expand All @@ -208,13 +270,16 @@ def __getitem__(self, item):
"""
Fetch attributes as separate outputs.
datajoint.key is a special value that requests the entire primary key

:return: tuple with an entry for each element of item

Examples:
a, b = relation['a', 'b']
a, b, key = relation['a', 'b', datajoint.key]
results = relation['a':'z'] # return attributes a-z as a tuple
results = relation[:-1] # return all but the last attribute

>>> a, b = relation['a', 'b']
>>> a, b, key = relation['a', 'b', datajoint.key]
>>> results = relation['a':'z'] # return attributes a-z as a tuple
>>> results = relation[:-1] # return all but the last attribute

"""
single_output = isinstance(item, str) or item is PRIMARY_KEY or isinstance(item, int)
item, attributes = prepare_attributes(self._relation, item)
Expand Down
2 changes: 1 addition & 1 deletion datajoint/kill.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def kill(restriction=None, connection=None):
except TypeError as err:
print(process)

response = input('process to kill or "q" to quit)')
response = input('process to kill or "q" to quit > ')
if response == 'q':
break
if response:
Expand Down
Loading