Skip to content

Commit 952607c

Browse files
Merge pull request #850 from dimitri-yatsenko/cascade-delete
Pre-release 0.13dev2 candidate
2 parents 6bae9ce + 8ca8594 commit 952607c

24 files changed

+444
-243
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ''
5+
labels: 'bug, awaiting-triage'
6+
assignees: ''
7+
8+
---
9+
10+
## Bug Report
11+
12+
### Description
13+
A clear and concise description of what is the overall operation that is intended to be performed that resulted in an error.
14+
15+
### Reproducibility
16+
Include:
17+
- OS (WIN | MACOS | Linux)
18+
- Python Version OR MATLAB Version
19+
- MySQL Version
20+
- MySQL Deployment Strategy (local-native | local-docker | remote)
21+
- DataJoint Version
22+
- Minimum number of steps to reliably reproduce the issue
23+
- Complete error stack as a result of evaluating the above steps
24+
25+
### Expected Behavior
26+
A clear and concise description of what you expected to happen.
27+
28+
### Screenshots
29+
If applicable, add screenshots to help explain your problem.
30+
31+
### Additional Research and Context
32+
Add any additional research or context that was conducted in creating this report.
33+
34+
For example:
35+
- Related GitHub issues and PR's either within this repository or in other relevant repositories.
36+
- Specific links to specific lines or a focus within source code.
37+
- Relevant summary of Maintainers development meetings, milestones, projects, etc.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
name: Feature request
3+
about: Suggest an idea for a new feature
4+
title: ''
5+
labels: 'enhancement, awaiting-triage'
6+
assignees: ''
7+
8+
---
9+
10+
## Feature Request
11+
12+
### Problem
13+
A clear and concise description how this idea has manifested and the context. Elaborate on the need for this feature and/or what could be improved. Ex. I'm always frustrated when [...]
14+
15+
### Requirements
16+
A clear and concise description of the requirements to satisfy the new feature. Detail what you expect from a successful implementation of the feature. Ex. When using this feature, it should [...]
17+
18+
### Justification
19+
Provide the key benefits in making this a supported feature. Ex. Adding support for this feature would ensure [...]
20+
21+
### Alternative Considerations
22+
Do you currently have a work-around for this? Provide any alternative solutions or features you've considered.
23+
24+
### Related Errors
25+
Add any errors as a direct result of not exposing this feature.
26+
27+
Please include steps to reproduce provided errors as follows:
28+
- OS (WIN | MACOS | Linux)
29+
- Python Version OR MATLAB Version
30+
- MySQL Version
31+
- MySQL Deployment Strategy (local-native | local-docker | remote)
32+
- DataJoint Version
33+
- Minimum number of steps to reliably reproduce the issue
34+
- Complete error stack as a result of evaluating the above steps
35+
36+
### Screenshots
37+
If applicable, add screenshots to help explain your feature.
38+
39+
### Additional Research and Context
40+
Add any additional research or context that was conducted in creating this feature request.
41+
42+
For example:
43+
- Related GitHub issues and PR's either within this repository or in other relevant repositories.
44+
- Specific links to specific line or focus within source code.
45+
- Relevant summary of Maintainers development meetings, milestones, projects, etc.
46+
- Any additional supplemental web references or links that would further justify this feature request.

.github/workflows/development.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: Development
2+
on:
3+
push:
4+
branches:
5+
- '**' # every branch
6+
- '!stage*' # exclude branches beginning with stage
7+
pull_request:
8+
branches:
9+
- '**' # every branch
10+
- '!stage*' # exclude branches beginning with stage
11+
jobs:
12+
test:
13+
if: github.event_name == 'push' || github.event_name == 'pull_request'
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
py_ver: ["3.8"]
18+
mysql_ver: ["8.0", "5.7", "5.6"]
19+
include:
20+
- py_ver: "3.7"
21+
mysql_ver: "5.7"
22+
- py_ver: "3.6"
23+
mysql_ver: "5.7"
24+
- py_ver: "3.5"
25+
mysql_ver: "5.7"
26+
steps:
27+
- uses: actions/checkout@v2
28+
- name: Set up Python ${{matrix.py_ver}}
29+
uses: actions/setup-python@v2
30+
with:
31+
python-version: ${{matrix.py_ver}}
32+
- name: Install dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
pip install flake8
36+
- name: Run syntax tests
37+
run: flake8 datajoint --count --select=E9,F63,F7,F82 --show-source --statistics
38+
- name: Run primary tests
39+
env:
40+
UID: "1001"
41+
GID: "116"
42+
PY_VER: ${{matrix.py_ver}}
43+
MYSQL_VER: ${{matrix.mysql_ver}}
44+
ALPINE_VER: "3.10"
45+
MINIO_VER: RELEASE.2019-09-26T19-42-35Z
46+
COMPOSE_HTTP_TIMEOUT: "120"
47+
COVERALLS_SERVICE_NAME: travis-ci
48+
COVERALLS_REPO_TOKEN: fd0BoXG46TPReEem0uMy7BJO5j0w1MQiY
49+
run: docker-compose -f LNX-docker-compose.yml up --build --exit-code-from app
50+
- name: Run style tests
51+
run: |
52+
flake8 --ignore=E121,E123,E126,E226,E24,E704,W503,W504,E722,F401,W605 datajoint \
53+
--count --max-complexity=62 --max-line-length=127 --statistics

.travis.yml

Lines changed: 0 additions & 59 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
## Release notes
22

3-
### 0.13.0 -- Dec 15, 2020
3+
### 0.13.0 -- Jan 11, 2020
44
* Re-implement query transpilation into SQL, fixing issues (#386, #449, #450, #484). PR #754
55
* Re-implement cascading deletes for better performance. PR #839.
66
* Add table method `.update1` to update an existing row in its table.
77
* Python datatypes are now enabled by default in blobs (#761). PR #785
88
* Added permissive join and restriction operators `@` and `^` (#785) PR #754
99

10-
### 0.12.8 -- Nov 30, 2020
11-
* table.children, .parents, .descendents, and ancestors optionally return queryable objects. PR #833
10+
### 0.12.8 -- Dec 22, 2020
11+
* table.children, .parents, .descendents, and ancestors can return queryable objects. PR #833
12+
* Load dependencies before querying dependencies. (#179) PR #833
13+
* Fix display of part tables in `schema.save`. (#821) PR #833
14+
* Add `schema.list_tables`. (#838) PR #844
15+
* Fix minio new version regression. PR #847
16+
* Add more S3 logging for debugging. (#831) PR #832
17+
* Convert testing framework from TravisCI to GitHub Actions (#841) PR #840
1218

1319
### 0.12.7 -- Oct 27, 2020
1420
* Fix case sensitivity issues to adapt to MySQL 8+. PR #819

datajoint/autopopulate.py

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from tqdm import tqdm
88
from .expression import QueryExpression, AndList
99
from .errors import DataJointError, LostConnectionError
10-
from .table import FreeTable
1110
import signal
1211

1312
# noinspection PyExceptionInherit,PyCallingNonCallable
@@ -27,30 +26,24 @@ class AutoPopulate:
2726
@property
2827
def key_source(self):
2928
"""
30-
:return: the query whose primary key values are passed, sequentially, to the
31-
`make` method when populate() is called.
29+
:return: the relation whose primary key values are passed, sequentially, to the
30+
``make`` method when populate() is called.
3231
The default value is the join of the parent relations.
3332
Users may override to change the granularity or the scope of populate() calls.
3433
"""
35-
def parent_gen():
36-
if self.target.full_table_name not in self.connection.dependencies:
37-
self.connection.dependencies.load()
38-
for parent_name, fk_props in self.target.parents(primary=True).items():
39-
if not parent_name.isdigit(): # simple foreign key
40-
yield FreeTable(self.connection, parent_name).proj()
41-
else:
42-
grandparent = list(self.connection.dependencies.in_edges(parent_name))[0][0]
43-
yield FreeTable(self.connection, grandparent).proj(**{
44-
attr: ref for attr, ref in fk_props['attr_map'].items() if ref != attr})
34+
def _rename_attributes(table, props):
35+
return (table.proj(
36+
**{attr: ref for attr, ref in props['attr_map'].items() if attr != ref})
37+
if props['aliased'] else table)
4538

4639
if self._key_source is None:
47-
parents = parent_gen()
48-
try:
49-
self._key_source = next(parents)
50-
except StopIteration:
51-
raise DataJointError('A relation must have primary dependencies for auto-populate to work') from None
52-
for q in parents:
53-
self._key_source *= q
40+
parents = self.target.parents(primary=True, as_objects=True, foreign_key_info=True)
41+
if not parents:
42+
raise DataJointError(
43+
'A relation must have primary dependencies for auto-populate to work') from None
44+
self._key_source = _rename_attributes(*parents[0])
45+
for q in parents[1:]:
46+
self._key_source *= _rename_attributes(*q)
5447
return self._key_source
5548

5649
def make(self, key):

datajoint/dependencies.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,36 @@
11
import networkx as nx
22
import itertools
3+
import re
34
from collections import defaultdict, OrderedDict
45
from .errors import DataJointError
56

67

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+
else:
30+
raise DataJointError("Found a part table {name} without its master table.".format(name=name))
31+
return lst
32+
33+
734
class Dependencies(nx.DiGraph):
835
"""
936
The graph of dependencies (foreign keys) between loaded tables.
@@ -118,8 +145,8 @@ def descendants(self, full_table_name):
118145
self.load(force=False)
119146
nodes = self.subgraph(
120147
nx.algorithms.dag.descendants(self, full_table_name))
121-
return [full_table_name] + list(
122-
nx.algorithms.dag.topological_sort(nodes))
148+
return unite_master_parts([full_table_name] + list(
149+
nx.algorithms.dag.topological_sort(nodes)))
123150

124151
def ancestors(self, full_table_name):
125152
"""
@@ -129,5 +156,5 @@ def ancestors(self, full_table_name):
129156
self.load(force=False)
130157
nodes = self.subgraph(
131158
nx.algorithms.dag.ancestors(self, full_table_name))
132-
return [full_table_name] + list(reversed(list(
133-
nx.algorithms.dag.topological_sort(nodes))))
159+
return list(reversed(unite_master_parts(list(
160+
nx.algorithms.dag.topological_sort(nodes)) + [full_table_name])))

datajoint/diagram.py

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import warnings
66
import inspect
77
from .table import Table
8+
from .dependencies import unite_master_parts
89

910
try:
1011
from matplotlib import pyplot as plt
@@ -155,29 +156,8 @@ def is_part(part, master):
155156
return self
156157

157158
def topological_sort(self):
158-
"""
159-
:return: list of nodes in topological order
160-
"""
161-
162-
def _unite(lst):
163-
"""
164-
reorder list so that parts immediately follow their masters without breaking the topological order.
165-
Without this correction, simple topological sort may insert other descendants between master and parts
166-
:example:
167-
_unite(['a', 'a__q', 'b', 'c', 'c__q', 'b__q', 'd', 'a__r'])
168-
-> ['a', 'a__q', 'a__r', 'b', 'b__q', 'c', 'c__q', 'd']
169-
"""
170-
if len(lst) <= 2:
171-
return lst
172-
el = lst.pop()
173-
lst = _unite(lst)
174-
if '__' in el:
175-
master = el.split('__')[0]
176-
if not lst[-1].startswith(master):
177-
return _unite(lst[:-1] + [el, lst[-1]])
178-
return lst + [el]
179-
180-
return _unite(list(nx.algorithms.dag.topological_sort(
159+
""" :return: list of nodes in topological order """
160+
return unite_master_parts(list(nx.algorithms.dag.topological_sort(
181161
nx.DiGraph(self).subgraph(self.nodes_to_show))))
182162

183163
def __add__(self, arg):

0 commit comments

Comments
 (0)