mirror of https://github.com/django/django.git
Fixed #32528 -- Replaced django.utils.topological_sort with graphlib.TopologicalSort().
graphlib.TopologicalSort() is available since Python 3.9.
This commit is contained in:
parent
4470c2405c
commit
1282b5e420
|
@ -1,6 +1,7 @@
|
|||
import functools
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from graphlib import TopologicalSorter
|
||||
from itertools import chain
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -15,7 +16,6 @@ from django.db.migrations.utils import (
|
|||
RegexObject,
|
||||
resolve_relation,
|
||||
)
|
||||
from django.utils.topological_sort import stable_topological_sort
|
||||
|
||||
|
||||
class MigrationAutodetector:
|
||||
|
@ -384,9 +384,9 @@ class MigrationAutodetector:
|
|||
nicely inside the same app.
|
||||
"""
|
||||
for app_label, ops in sorted(self.generated_operations.items()):
|
||||
# construct a dependency graph for intra-app dependencies
|
||||
dependency_graph = {op: set() for op in ops}
|
||||
ts = TopologicalSorter()
|
||||
for op in ops:
|
||||
ts.add(op)
|
||||
for dep in op._auto_deps:
|
||||
# Resolve intra-app dependencies to handle circular
|
||||
# references involving a swappable model.
|
||||
|
@ -394,12 +394,8 @@ class MigrationAutodetector:
|
|||
if dep[0] == app_label:
|
||||
for op2 in ops:
|
||||
if self.check_dependency(op2, dep):
|
||||
dependency_graph[op].add(op2)
|
||||
|
||||
# we use a stable sort for deterministic tests & general behavior
|
||||
self.generated_operations[app_label] = stable_topological_sort(
|
||||
ops, dependency_graph
|
||||
)
|
||||
ts.add(op, op2)
|
||||
self.generated_operations[app_label] = list(ts.static_order())
|
||||
|
||||
def _optimize_migrations(self):
|
||||
# Add in internal dependencies among the migrations
|
||||
|
|
|
@ -6,6 +6,7 @@ import copy
|
|||
import datetime
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from graphlib import CycleError, TopologicalSorter
|
||||
from itertools import chain
|
||||
|
||||
from django.forms.utils import to_current_timezone
|
||||
|
@ -17,7 +18,6 @@ from django.utils.formats import get_format
|
|||
from django.utils.html import format_html, html_safe
|
||||
from django.utils.regex_helper import _lazy_re_compile
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.topological_sort import CyclicDependencyError, stable_topological_sort
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .renderers import get_default_renderer
|
||||
|
@ -151,22 +151,22 @@ class Media:
|
|||
in a certain order. In JavaScript you may not be able to reference a
|
||||
global or in CSS you might want to override a style.
|
||||
"""
|
||||
dependency_graph = defaultdict(set)
|
||||
ts = TopologicalSorter()
|
||||
all_items = OrderedSet()
|
||||
for list_ in filter(None, lists):
|
||||
head = list_[0]
|
||||
# The first items depend on nothing but have to be part of the
|
||||
# dependency graph to be included in the result.
|
||||
dependency_graph.setdefault(head, set())
|
||||
ts.add(head)
|
||||
for item in list_:
|
||||
all_items.add(item)
|
||||
# No self dependencies
|
||||
if head != item:
|
||||
dependency_graph[item].add(head)
|
||||
ts.add(item, head)
|
||||
head = item
|
||||
try:
|
||||
return stable_topological_sort(all_items, dependency_graph)
|
||||
except CyclicDependencyError:
|
||||
return list(ts.static_order())
|
||||
except CycleError:
|
||||
warnings.warn(
|
||||
"Detected duplicate Media files in an opposite order: {}".format(
|
||||
", ".join(repr(list_) for list_ in lists)
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
class CyclicDependencyError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def topological_sort_as_sets(dependency_graph):
|
||||
"""
|
||||
Variation of Kahn's algorithm (1962) that returns sets.
|
||||
|
||||
Take a dependency graph as a dictionary of node => dependencies.
|
||||
|
||||
Yield sets of items in topological order, where the first set contains
|
||||
all nodes without dependencies, and each following set contains all
|
||||
nodes that may depend on the nodes only in the previously yielded sets.
|
||||
"""
|
||||
todo = dependency_graph.copy()
|
||||
while todo:
|
||||
current = {node for node, deps in todo.items() if not deps}
|
||||
|
||||
if not current:
|
||||
raise CyclicDependencyError(
|
||||
"Cyclic dependency in graph: {}".format(
|
||||
", ".join(repr(x) for x in todo.items())
|
||||
)
|
||||
)
|
||||
|
||||
yield current
|
||||
|
||||
# remove current from todo's nodes & dependencies
|
||||
todo = {
|
||||
node: (dependencies - current)
|
||||
for node, dependencies in todo.items()
|
||||
if node not in current
|
||||
}
|
||||
|
||||
|
||||
def stable_topological_sort(nodes, dependency_graph):
|
||||
result = []
|
||||
for layer in topological_sort_as_sets(dependency_graph):
|
||||
for node in nodes:
|
||||
if node in layer:
|
||||
result.append(node)
|
||||
return result
|
|
@ -2218,8 +2218,8 @@ class AutodetectorTests(BaseAutodetectorTests):
|
|||
# Right number/type of migrations?
|
||||
self.assertNumberMigrations(changes, "testapp", 1)
|
||||
self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel"])
|
||||
self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")
|
||||
self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")
|
||||
self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
|
||||
self.assertOperationAttributes(changes, "testapp", 0, 1, name="Publisher")
|
||||
self.assertMigrationDependencies(
|
||||
changes, "testapp", 0, [("otherapp", "auto_1")]
|
||||
)
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
from django.test import SimpleTestCase
|
||||
from django.utils.topological_sort import (
|
||||
CyclicDependencyError,
|
||||
stable_topological_sort,
|
||||
topological_sort_as_sets,
|
||||
)
|
||||
|
||||
|
||||
class TopologicalSortTests(SimpleTestCase):
|
||||
def test_basic(self):
|
||||
dependency_graph = {
|
||||
1: {2, 3},
|
||||
2: set(),
|
||||
3: set(),
|
||||
4: {5, 6},
|
||||
5: set(),
|
||||
6: {5},
|
||||
}
|
||||
self.assertEqual(
|
||||
list(topological_sort_as_sets(dependency_graph)), [{2, 3, 5}, {1, 6}, {4}]
|
||||
)
|
||||
self.assertEqual(
|
||||
stable_topological_sort([1, 2, 3, 4, 5, 6], dependency_graph),
|
||||
[2, 3, 5, 1, 6, 4],
|
||||
)
|
||||
|
||||
def test_cyclic_dependency(self):
|
||||
msg = "Cyclic dependency in graph: (1, {2}), (2, {1})"
|
||||
with self.assertRaisesMessage(CyclicDependencyError, msg):
|
||||
list(topological_sort_as_sets({1: {2}, 2: {1}}))
|
Loading…
Reference in New Issue