Skip to content

Commit 4fb4441

Browse files
Merge branch 'master' into key_populate
2 parents 1b44100 + 6719f4a commit 4fb4441

File tree

8 files changed

+22
-177
lines changed

8 files changed

+22
-177
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
- Added - Datajoint python CLI ([#940](https://github.com/datajoint/datajoint-python/issues/940)) PR [#1095](https://github.com/datajoint/datajoint-python/pull/1095)
77
- Added - Ability to set hidden attributes on a table - PR [#1091](https://github.com/datajoint/datajoint-python/pull/1091)
88
- Added - Ability to specify a list of keys to populate - PR [#989](https://github.com/datajoint/datajoint-python/pull/989)
9+
- Fixed - fixed topological sort [#1057](https://github.com/datajoint/datajoint-python/issues/1057)- PR [#1184](https://github.com/datajoint/datajoint-python/pull/1184)
10+
- Fixed - .parts() not always returning parts [#1103](https://github.com/datajoint/datajoint-python/issues/1103)- PR [#1184](https://github.com/datajoint/datajoint-python/pull/1184)
911

1012
### 0.14.2 -- Aug 19, 2024
1113
- Added - Migrate nosetests to pytest - PR [#1142](https://github.com/datajoint/datajoint-python/pull/1142)

datajoint/dependencies.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,9 @@
11
import networkx as nx
22
import itertools
3-
import re
43
from collections import defaultdict
54
from .errors import DataJointError
65

76

8-
def unite_master_parts(lst):
9-
"""
10-
re-order a list of table names so that part tables immediately follow their master tables without breaking
11-
the topological order.
12-
Without this correction, a simple topological sort may insert other descendants between master and parts.
13-
The input list must be topologically sorted.
14-
:example:
15-
unite_master_parts(
16-
['`s`.`a`', '`s`.`a__q`', '`s`.`b`', '`s`.`c`', '`s`.`c__q`', '`s`.`b__q`', '`s`.`d`', '`s`.`a__r`']) ->
17-
['`s`.`a`', '`s`.`a__q`', '`s`.`a__r`', '`s`.`b`', '`s`.`b__q`', '`s`.`c`', '`s`.`c__q`', '`s`.`d`']
18-
"""
19-
for i in range(2, len(lst)):
20-
name = lst[i]
21-
match = re.match(r"(?P<master>`\w+`.`#?\w+)__\w+`", name)
22-
if match: # name is a part table
23-
master = match.group("master")
24-
for j in range(i - 1, -1, -1):
25-
if lst[j] == master + "`" or lst[j].startswith(master + "__"):
26-
# move from the ith position to the (j+1)th position
27-
lst[j + 1 : i + 1] = [name] + lst[j + 1 : i]
28-
break
29-
return lst
30-
31-
327
class Dependencies(nx.DiGraph):
338
"""
349
The graph of dependencies (foreign keys) between loaded tables.
@@ -168,9 +143,7 @@ def descendants(self, full_table_name):
168143
"""
169144
self.load(force=False)
170145
nodes = self.subgraph(nx.algorithms.dag.descendants(self, full_table_name))
171-
return unite_master_parts(
172-
[full_table_name] + list(nx.algorithms.dag.topological_sort(nodes))
173-
)
146+
return [full_table_name] + list(nx.algorithms.dag.topological_sort(nodes))
174147

175148
def ancestors(self, full_table_name):
176149
"""
@@ -181,8 +154,6 @@ def ancestors(self, full_table_name):
181154
nodes = self.subgraph(nx.algorithms.dag.ancestors(self, full_table_name))
182155
return list(
183156
reversed(
184-
unite_master_parts(
185-
list(nx.algorithms.dag.topological_sort(nodes)) + [full_table_name]
186-
)
157+
list(nx.algorithms.dag.topological_sort(nodes)) + [full_table_name]
187158
)
188159
)

datajoint/diagram.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import logging
66
import inspect
77
from .table import Table
8-
from .dependencies import unite_master_parts
98
from .user_tables import Manual, Imported, Computed, Lookup, Part
109
from .errors import DataJointError
1110
from .table import lookup_class_name
@@ -59,8 +58,7 @@ class Diagram:
5958
Entity relationship diagram, currently disabled due to the lack of required packages: matplotlib and pygraphviz.
6059
6160
To enable Diagram feature, please install both matplotlib and pygraphviz. For instructions on how to install
62-
these two packages, refer to http://docs.datajoint.io/setup/Install-and-connect.html#python and
63-
http://tutorials.datajoint.io/setting-up/datajoint-python.html
61+
these two packages, refer to https://datajoint.com/docs/core/datajoint-python/0.14/client/install/
6462
"""
6563

6664
def __init__(self, *args, **kwargs):
@@ -181,11 +179,9 @@ def is_part(part, master):
181179

182180
def topological_sort(self):
183181
""":return: list of nodes in topological order"""
184-
return unite_master_parts(
185-
list(
186-
nx.algorithms.dag.topological_sort(
187-
nx.DiGraph(self).subgraph(self.nodes_to_show)
188-
)
182+
return list(
183+
nx.algorithms.dag.topological_sort(
184+
nx.DiGraph(self).subgraph(self.nodes_to_show)
189185
)
190186
)
191187

datajoint/schemas.py

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,16 @@
22
import logging
33
import inspect
44
import re
5-
import itertools
6-
import collections
75
from .connection import conn
8-
from .diagram import Diagram, _get_tier
6+
from .diagram import Diagram
97
from .settings import config
108
from .errors import DataJointError, AccessError
119
from .jobs import JobTable
1210
from .external import ExternalMapping
1311
from .heading import Heading
1412
from .utils import user_choice, to_camel_case
1513
from .user_tables import Part, Computed, Imported, Manual, Lookup
16-
from .table import lookup_class_name, Log, FreeTable
14+
from .table import lookup_class_name, Log
1715
import types
1816

1917
logger = logging.getLogger(__name__.split(".")[0])
@@ -401,78 +399,6 @@ def jobs(self):
401399
self._jobs = JobTable(self.connection, self.database)
402400
return self._jobs
403401

404-
@property
405-
def code(self):
406-
self._assert_exists()
407-
return self.save()
408-
409-
def save(self, python_filename=None):
410-
"""
411-
Generate the code for a module that recreates the schema.
412-
This method is in preparation for a future release and is not officially supported.
413-
414-
:return: a string containing the body of a complete Python module defining this schema.
415-
"""
416-
self._assert_exists()
417-
module_count = itertools.count()
418-
# add virtual modules for referenced modules with names vmod0, vmod1, ...
419-
module_lookup = collections.defaultdict(
420-
lambda: "vmod" + str(next(module_count))
421-
)
422-
db = self.database
423-
424-
def make_class_definition(table):
425-
tier = _get_tier(table).__name__
426-
class_name = table.split(".")[1].strip("`")
427-
indent = ""
428-
if tier == "Part":
429-
class_name = class_name.split("__")[-1]
430-
indent += " "
431-
class_name = to_camel_case(class_name)
432-
433-
def replace(s):
434-
d, tabs = s.group(1), s.group(2)
435-
return ("" if d == db else (module_lookup[d] + ".")) + ".".join(
436-
to_camel_case(tab) for tab in tabs.lstrip("__").split("__")
437-
)
438-
439-
return ("" if tier == "Part" else "\n@schema\n") + (
440-
"{indent}class {class_name}(dj.{tier}):\n"
441-
'{indent} definition = """\n'
442-
'{indent} {defi}"""'
443-
).format(
444-
class_name=class_name,
445-
indent=indent,
446-
tier=tier,
447-
defi=re.sub(
448-
r"`([^`]+)`.`([^`]+)`",
449-
replace,
450-
FreeTable(self.connection, table).describe(),
451-
).replace("\n", "\n " + indent),
452-
)
453-
454-
diagram = Diagram(self)
455-
body = "\n\n".join(
456-
make_class_definition(table) for table in diagram.topological_sort()
457-
)
458-
python_code = "\n\n".join(
459-
(
460-
'"""This module was auto-generated by datajoint from an existing schema"""',
461-
"import datajoint as dj\n\nschema = dj.Schema('{db}')".format(db=db),
462-
"\n".join(
463-
"{module} = dj.VirtualModule('{module}', '{schema_name}')".format(
464-
module=v, schema_name=k
465-
)
466-
for k, v in module_lookup.items()
467-
),
468-
body,
469-
)
470-
)
471-
if python_filename is None:
472-
return python_code
473-
with open(python_filename, "wt") as f:
474-
f.write(python_code)
475-
476402
def list_tables(self):
477403
"""
478404
Return a list of all tables in the schema except tables with ~ in first character such

datajoint/table.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,6 @@ def parents(self, primary=None, as_objects=False, foreign_key_info=False):
196196

197197
def children(self, primary=None, as_objects=False, foreign_key_info=False):
198198
"""
199-
200199
:param primary: if None, then all children are returned. If True, then only foreign keys composed of
201200
primary key attributes are considered. If False, return foreign keys including at least one
202201
secondary attribute.
@@ -230,7 +229,6 @@ def descendants(self, as_objects=False):
230229

231230
def ancestors(self, as_objects=False):
232231
"""
233-
234232
:param as_objects: False - a list of table names; True - a list of table objects.
235233
:return: list of tables ancestors in topological order.
236234
"""
@@ -246,6 +244,7 @@ def parts(self, as_objects=False):
246244
247245
:param as_objects: if False (default), the output is a dict describing the foreign keys. If True, return table objects.
248246
"""
247+
self.connection.dependencies.load(force=False)
249248
nodes = [
250249
node
251250
for node in self.connection.dependencies.nodes
@@ -427,7 +426,8 @@ def insert(
427426
self.connection.query(query)
428427
return
429428

430-
field_list = [] # collects the field list from first row (passed by reference)
429+
# collects the field list from first row (passed by reference)
430+
field_list = []
431431
rows = list(
432432
self.__make_row_to_insert(row, field_list, ignore_extra_fields)
433433
for row in rows
@@ -520,7 +520,8 @@ def cascade(table):
520520
delete_count = table.delete_quick(get_count=True)
521521
except IntegrityError as error:
522522
match = foreign_key_error_regexp.match(error.args[0]).groupdict()
523-
if "`.`" not in match["child"]: # if schema name missing, use table
523+
# if schema name missing, use table
524+
if "`.`" not in match["child"]:
524525
match["child"] = "{}.{}".format(
525526
table.full_table_name.split(".")[0], match["child"]
526527
)
@@ -964,7 +965,8 @@ def lookup_class_name(name, context, depth=3):
964965
while nodes:
965966
node = nodes.pop(0)
966967
for member_name, member in node["context"].items():
967-
if not member_name.startswith("_"): # skip IPython's implicit variables
968+
# skip IPython's implicit variables
969+
if not member_name.startswith("_"):
968970
if inspect.isclass(member) and issubclass(member, Table):
969971
if member.full_table_name == name: # found it!
970972
return ".".join([node["context_name"], member_name]).lstrip(".")

tests/test_blob.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ def test_insert_longblob(schema_any):
185185

186186
query_mym_blob = {"id": 1, "data": np.array([1, 2, 3])}
187187
Longblob.insert1(query_mym_blob)
188-
assert (Longblob & "id=1").fetch1()["data"].all() == query_mym_blob["data"].all()
188+
assert_array_equal((Longblob & "id=1").fetch1()["data"], query_mym_blob["data"])
189189
(Longblob & "id=1").delete()
190190

191191

@@ -218,7 +218,8 @@ def test_insert_longblob_32bit(schema_any, enable_feature_32bit_dims):
218218
),
219219
}
220220
assert fetched["id"] == expected["id"]
221-
assert np.array_equal(fetched["data"], expected["data"])
221+
for name in expected["data"][0][0].dtype.names:
222+
assert_array_equal(expected["data"][0][0][name], fetched["data"][0][0][name])
222223
(Longblob & "id=1").delete()
223224

224225

@@ -248,4 +249,5 @@ def test_datetime_serialization_speed():
248249
)
249250
print(f"python time {baseline_exe_time}")
250251

251-
assert optimized_exe_time * 900 < baseline_exe_time
252+
# The time savings were much greater (x1000) but use x10 for testing
253+
assert optimized_exe_time * 10 < baseline_exe_time

tests/test_dependencies.py

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,5 @@
11
from datajoint import errors
22
from pytest import raises
3-
from datajoint.dependencies import unite_master_parts
4-
5-
6-
def test_unite_master_parts():
7-
assert unite_master_parts(
8-
[
9-
"`s`.`a`",
10-
"`s`.`a__q`",
11-
"`s`.`b`",
12-
"`s`.`c`",
13-
"`s`.`c__q`",
14-
"`s`.`b__q`",
15-
"`s`.`d`",
16-
"`s`.`a__r`",
17-
]
18-
) == [
19-
"`s`.`a`",
20-
"`s`.`a__q`",
21-
"`s`.`a__r`",
22-
"`s`.`b`",
23-
"`s`.`b__q`",
24-
"`s`.`c`",
25-
"`s`.`c__q`",
26-
"`s`.`d`",
27-
]
28-
assert unite_master_parts(
29-
[
30-
"`lab`.`#equipment`",
31-
"`cells`.`cell_analysis_method`",
32-
"`cells`.`cell_analysis_method_task_type`",
33-
"`cells`.`cell_analysis_method_users`",
34-
"`cells`.`favorite_selection`",
35-
"`cells`.`cell_analysis_method__cell_selection_params`",
36-
"`lab`.`#equipment__config`",
37-
"`cells`.`cell_analysis_method__field_detect_params`",
38-
]
39-
) == [
40-
"`lab`.`#equipment`",
41-
"`lab`.`#equipment__config`",
42-
"`cells`.`cell_analysis_method`",
43-
"`cells`.`cell_analysis_method__cell_selection_params`",
44-
"`cells`.`cell_analysis_method__field_detect_params`",
45-
"`cells`.`cell_analysis_method_task_type`",
46-
"`cells`.`cell_analysis_method_users`",
47-
"`cells`.`favorite_selection`",
48-
]
493

504

515
def test_nullable_dependency(thing_tables):

tests/test_schema.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,6 @@ def test_list_tables(schema_simp):
218218
assert actual == expected, f"Missing from list_tables(): {expected - actual}"
219219

220220

221-
def test_schema_save_any(schema_any):
222-
assert "class Experiment(dj.Imported)" in schema_any.code
223-
224-
225-
def test_schema_save_empty(schema_empty):
226-
assert "class Experiment(dj.Imported)" in schema_empty.code
227-
228-
229221
def test_uppercase_schema(db_creds_root):
230222
"""
231223
https://github.com/datajoint/datajoint-python/issues/564

0 commit comments

Comments
 (0)