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
18 changes: 9 additions & 9 deletions datajoint/base.py → datajoint/Relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,24 @@
import abc
from types import ModuleType
from . import DataJointError
from .table import Table
from .free_relation import FreeRelation
import logging


logger = logging.getLogger(__name__)


class Base(Table, metaclass=abc.ABCMeta):
class Relation(FreeRelation, metaclass=abc.ABCMeta):
"""
Base is a Table that implements data definition functions.
Relation is a Table that implements data definition functions.
It is an abstract class with the abstract property 'definition'.

Example for a usage of Base::
Example for a usage of Relation::

import datajoint as dj


class Subjects(dj.Base):
class Subjects(dj.Relation):
definition = '''
test1.Subjects (manual) # Basic subject info
subject_id : int # unique subject id
Expand Down Expand Up @@ -91,7 +91,7 @@ def __init__(self): #TODO: support taking in conn obj
def get_base(self, module_name, class_name):
"""
Loads the base relation from the module. If the base relation is not defined in
the module, then construct it using Base constructor.
the module, then construct it using Relation constructor.

:param module_name: module name
:param class_name: class name
Expand All @@ -104,7 +104,7 @@ def get_base(self, module_name, class_name):
try:
ret = getattr(mod_obj, class_name)()
except AttributeError:
ret = Table(conn=self.conn,
ret = FreeRelation(conn=self.conn,
dbname=self.conn.mod_to_db[mod_obj.__name__],
class_name=class_name)
return ret
Expand All @@ -119,9 +119,9 @@ def get_module(cls, module_name):

The module_name resolution steps in the following order:

1. Global reference to a module of the same name defined in the module that contains this Base derivative.
1. Global reference to a module of the same name defined in the module that contains this Relation derivative.
This is the recommended use case.
2. Module of the same name defined in the package containing this Base derivative. This will only look for the
2. Module of the same name defined in the package containing this Relation derivative. This will only look for the
most immediate containing package (e.g. if this class is contained in package.subpackage.module, it will
check within `package.subpackage` but not inside `package`).
3. Globally accessible module with the same name.
Expand Down
6 changes: 3 additions & 3 deletions datajoint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__author__ = "Dimitri Yatsenko, Edgar Walker, and Fabian Sinz at Baylor College of Medicine"
__version__ = "0.2"
__all__ = ['__author__', '__version__',
'Connection', 'Heading', 'Base', 'Not',
'Connection', 'Heading', 'Relation', 'Not',
'AutoPopulate', 'conn', 'DataJointError', 'blob']


Expand Down Expand Up @@ -34,7 +34,7 @@ class DataJointError(Exception):

# ------------- flatten import hierarchy -------------------------
from .connection import conn, Connection
from .base import Base
from .relation import Relation
from .autopopulate import AutoPopulate
from . import blob
from .relational import Not
from .relational_operand import Not
8 changes: 4 additions & 4 deletions datajoint/autopopulate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .relational import Relation
from .relational_operand import RelationalOperand
from . import DataJointError
import pprint
import abc
Expand All @@ -10,8 +10,8 @@

class AutoPopulate(metaclass=abc.ABCMeta):
"""
AutoPopulate is a mixin class that adds the method populate() to a Base class.
Auto-populated relations must inherit from both Base and AutoPopulate,
AutoPopulate is a mixin class that adds the method populate() to a Relation class.
Auto-populated relations must inherit from both Relation and AutoPopulate,
must define the property pop_rel, and must define the callback method make_tuples.
"""

Expand Down Expand Up @@ -40,7 +40,7 @@ def populate(self, catch_errors=False, reserve_jobs=False, restrict=None):
rel.populate() will call rel.make_tuples(key) for every primary key in self.pop_rel
for which there is not already a tuple in rel.
"""
if not isinstance(self.pop_rel, Relation):
if not isinstance(self.pop_rel, RelationalOperand):
raise DataJointError('')
self.conn.cancel_transaction()

Expand Down
22 changes: 11 additions & 11 deletions datajoint/table.py → datajoint/free_relation.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import numpy as np
import logging
from . import DataJointError
from .relational import Relation
from .relational_operand import RelationalOperand
from .blob import pack
from .heading import Heading
import re
Expand All @@ -11,20 +11,20 @@
logger = logging.getLogger(__name__)


class Table(Relation):
class FreeRelation(RelationalOperand):
"""
A Table object is a relation associated with a table.
A Table object provides insert and delete methods.
Table objects are only used internally and for debugging.
The table must already exist in the schema for its Table object to work.
A FreeRelation object is a relation associated with a table.
A FreeRelation object provides insert and delete methods.
FreeRelation objects are only used internally and for debugging.
The table must already exist in the schema for its FreeRelation object to work.

The table associated with an instance of Base is identified by its 'class name'.
The table associated with an instance of Relation is identified by its 'class name'.
property, which is a string in CamelCase. The actual table name is obtained
by converting className from CamelCase to underscore_separated_words and
prefixing according to the table's role.

Base instances obtain their table's heading by looking it up in the connection
object. This ensures that Base instances contain the current table definition
Relation instances obtain their table's heading by looking it up in the connection
object. This ensures that Relation instances contain the current table definition
even after tables are modified after the instance is created.
"""

Expand Down Expand Up @@ -62,7 +62,7 @@ def declare(self):
self._declare()
if not self.is_declared:
raise DataJointError(
'Table could not be declared for %s' % self.class_name)
'FreeRelation could not be declared for %s' % self.class_name)

@staticmethod
def _field_to_sql(field): #TODO move this into Attribute Tuple
Expand Down Expand Up @@ -360,7 +360,7 @@ def get_base(self, module_name, class_name):
m = re.match(r'`(\w+)`', module_name)
if m:
dbname = m.group(1)
return Table(self.conn, dbname, class_name)
return FreeRelation(self.conn, dbname, class_name)
else:
return None

Expand Down
26 changes: 13 additions & 13 deletions datajoint/relational.py → datajoint/relational_operand.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
logger = logging.getLogger(__name__)


class Relation(metaclass=abc.ABCMeta):
class RelationalOperand(metaclass=abc.ABCMeta):
"""
Relation implements relational algebra and fetch methods.
Relation objects reference other relation objects linked by operators.
RelationalOperand implements relational algebra and fetch methods.
RelationalOperand objects reference other relation objects linked by operators.
The leaves of this tree of objects are base relations.
When fetching data from the database, this tree of objects is compiled into an SQL expression.
It is a mixin class that provides relational operators, iteration, and fetch capability.
Relation operators are: restrict, pro, and join.
RelationalOperand operators are: restrict, pro, and join.
"""
_restrictions = []

Expand Down Expand Up @@ -53,7 +53,7 @@ def __mul__(self, other):

def __mod__(self, attributes=None):
"""
relational projection operator. See Relation.project
relational projection operator. See RelationalOperand.project
"""
return self.project(*attributes)

Expand All @@ -70,7 +70,7 @@ def project(self, *attributes, **renamed_attributes):
"""
# if the first attribute is a relation, it will be aggregated
group = attributes.pop[0] \
if attributes and isinstance(attributes[0], Relation) else None
if attributes and isinstance(attributes[0], RelationalOperand) else None
return self.aggregate(group, *attributes, **renamed_attributes)

def aggregate(self, _group, *attributes, **renamed_attributes):
Expand All @@ -80,7 +80,7 @@ def aggregate(self, _group, *attributes, **renamed_attributes):
:param extensions:
:return: a relation representing the aggregation/projection operator result
"""
if _group is not None and not isinstance(_group, Relation):
if _group is not None and not isinstance(_group, RelationalOperand):
raise DataJointError('The second argument must be a relation or None')
alias_parser = re.compile(
'^\s*(?P<sql_expression>\S(.*\S)?)\s*->\s*(?P<alias>[a-z][a-z_0-9]*)\s*$')
Expand Down Expand Up @@ -229,7 +229,7 @@ def make_condition(arg):
r = make_condition(r)
elif isinstance(r, np.ndarray) or isinstance(r, list):
r = '('+') OR ('.join([make_condition(q) for q in r])+')'
elif isinstance(r, Relation):
elif isinstance(r, RelationalOperand):
common_attributes = ','.join([q for q in self.heading.names if r.heading.names])
r = '(%s) in (SELECT %s FROM %s)' % (common_attributes, common_attributes, r.sql)

Expand All @@ -254,11 +254,11 @@ def restriction(self):
return self.__restriction


class Join(Relation):
class Join(RelationalOperand):
subquery_counter = 0

def __init__(self, rel1, rel2):
if not isinstance(rel2, Relation):
if not isinstance(rel2, RelationalOperand):
raise DataJointError('a relation can only be joined with another relation')
if rel1.conn is not rel2.conn:
raise DataJointError('Cannot join relations with different database connections')
Expand All @@ -284,12 +284,12 @@ def sql(self):
return '%s NATURAL JOIN %s as `_j%x`' % (self._rel1.sql, self._rel2.sql, self.counter)


class Projection(Relation):
class Projection(RelationalOperand):
subquery_counter = 0

def __init__(self, relation, group=None, *attributes, **renamed_attributes):
"""
See Relation.project()
See RelationalOperand.project()
"""
if group:
if relation.conn is not group.conn:
Expand Down Expand Up @@ -317,7 +317,7 @@ def sql(self):
return sql, heading


class Subquery(Relation):
class Subquery(RelationalOperand):
"""
A Subquery encapsulates its argument in a SELECT statement, enabling its use as a subquery.
The attribute list and the WHERE clause are resolved.
Expand Down
8 changes: 4 additions & 4 deletions demos/demo1.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
conn.bind(module=__name__, dbname='dj_test') # bind this module to the database


class Subject(dj.Base):
class Subject(dj.Relation):
definition = """
demo1.Subject (manual) # Basic subject info
subject_id : int # internal subject id
Expand All @@ -28,7 +28,7 @@ class Subject(dj.Base):
"""


class Experiment(dj.Base):
class Experiment(dj.Relation):
definition = """
demo1.Experiment (manual) # Basic subject info
-> demo1.Subject
Expand All @@ -41,7 +41,7 @@ class Experiment(dj.Base):
"""


class Session(dj.Base):
class Session(dj.Relation):
definition = """
demo1.Session (manual) # a two-photon imaging session
-> demo1.Experiment
Expand All @@ -52,7 +52,7 @@ class Session(dj.Base):
"""


class Scan(dj.Base):
class Scan(dj.Relation):
definition = """
demo1.Scan (manual) # a two-photon imaging session
-> demo1.Session
Expand Down
2 changes: 1 addition & 1 deletion doc/source/Getting started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This tests some latex :math:`x \mapsto y`.
environ.get('DJ_PASSW', 'datajoint')
import datajoint as dj

class Subjects(dj.Base):
class Subjects(dj.Relation):
_table_def = """
Subjects (manual) # Basic subject info

Expand Down
2 changes: 1 addition & 1 deletion doc/source/base.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Base
Relation
====

.. automodule:: datajoint.base
Expand Down
12 changes: 6 additions & 6 deletions tests/schemata/schema1/test1.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import datajoint as dj
from .. import schema2

class Subjects(dj.Base):
class Subjects(dj.Relation):
definition = """
test1.Subjects (manual) # Basic subject info

Expand All @@ -17,7 +17,7 @@ class Subjects(dj.Base):
"""

# test reference to another table in same schema
class Experiments(dj.Base):
class Experiments(dj.Relation):
definition = """
test1.Experiments (imported) # Experiment info
-> test1.Subjects
Expand All @@ -27,7 +27,7 @@ class Experiments(dj.Base):
"""

# refers to a table in dj_test2 (bound to test2) but without a class
class Sessions(dj.Base):
class Sessions(dj.Relation):
definition = """
test1.Sessions (manual) # Experiment sessions
-> test1.Subjects
Expand All @@ -37,7 +37,7 @@ class Sessions(dj.Base):
session_comment : varchar(255) # comment about the session
"""

class Match(dj.Base):
class Match(dj.Relation):
definition = """
test1.Match (manual) # Match between subject and color
-> schema2.Subjects
Expand All @@ -46,12 +46,12 @@ class Match(dj.Base):
"""

# this tries to reference a table in database directly without ORM
class TrainingSession(dj.Base):
class TrainingSession(dj.Relation):
definition = """
test1.TrainingSession (manual) # training sessions
-> `dj_test2`.Experimenter
session_id : int # training session id
"""

class Empty(dj.Base):
class Empty(dj.Relation):
pass
8 changes: 4 additions & 4 deletions tests/schemata/schema1/test2.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@


# references to another schema
class Experiments(dj.Base):
class Experiments(dj.Relation):
definition = """
test2.Experiments (manual) # Basic subject info
-> test1.Subjects
Expand All @@ -20,21 +20,21 @@ class Experiments(dj.Base):
"""

# references to another schema
class Conditions(dj.Base):
class Conditions(dj.Relation):
definition = """
test2.Conditions (manual) # Subject conditions
-> alias.Subjects
condition_name : varchar(255) # description of the condition
"""

class FoodPreference(dj.Base):
class FoodPreference(dj.Relation):
definition = """
test2.FoodPreference (manual) # Food preference of each subject
-> animals.Subjects
preferred_food : enum('banana', 'apple', 'oranges')
"""

class Session(dj.Base):
class Session(dj.Relation):
definition = """
test2.Session (manual) # Experiment sessions
-> test1.Subjects
Expand Down
Loading