Represent the digraph of all migrations in a project. Each migration is a node, and each dependency is an edge. There are no implicit dependencies between numbered migrations - the numbering is merely a convention to aid file listing. Every new numbered migration has a declared
| 62 | |
| 63 | |
| 64 | class MigrationGraph: |
| 65 | """ |
| 66 | Represent the digraph of all migrations in a project. |
| 67 | |
| 68 | Each migration is a node, and each dependency is an edge. There are |
| 69 | no implicit dependencies between numbered migrations - the numbering is |
| 70 | merely a convention to aid file listing. Every new numbered migration |
| 71 | has a declared dependency to the previous number, meaning that VCS |
| 72 | branch merges can be detected and resolved. |
| 73 | |
| 74 | Migrations files can be marked as replacing another set of migrations - |
| 75 | this is to support the "squash" feature. The graph handler isn't |
| 76 | responsible for these; instead, the code to load them in here should |
| 77 | examine the migration files and if the replaced migrations are all either |
| 78 | unapplied or not present, it should ignore the replaced ones, load in just |
| 79 | the replacing migration, and repoint any dependencies that pointed to the |
| 80 | replaced migrations to point to the replacing one. |
| 81 | |
| 82 | A node should be a tuple: (app_path, migration_name). The tree |
| 83 | special-cases things within an app - namely, root nodes and leaf nodes |
| 84 | ignore dependencies to other apps. |
| 85 | """ |
| 86 | |
| 87 | def __init__(self): |
| 88 | self.node_map = {} |
| 89 | self.nodes = {} |
| 90 | |
| 91 | def add_node(self, key, migration): |
| 92 | assert key not in self.node_map |
| 93 | node = Node(key) |
| 94 | self.node_map[key] = node |
| 95 | self.nodes[key] = migration |
| 96 | |
| 97 | def add_dummy_node(self, key, origin, error_message): |
| 98 | node = DummyNode(key, origin, error_message) |
| 99 | self.node_map[key] = node |
| 100 | self.nodes[key] = None |
| 101 | |
| 102 | def add_dependency(self, migration, child, parent, skip_validation=False): |
| 103 | """ |
| 104 | This may create dummy nodes if they don't yet exist. If |
| 105 | `skip_validation=True`, validate_consistency() should be called |
| 106 | afterward. |
| 107 | """ |
| 108 | if child not in self.nodes: |
| 109 | error_message = ( |
| 110 | "Migration %s dependencies reference nonexistent" |
| 111 | " child node %r" % (migration, child) |
| 112 | ) |
| 113 | self.add_dummy_node(child, migration, error_message) |
| 114 | if parent not in self.nodes: |
| 115 | error_message = ( |
| 116 | "Migration %s dependencies reference nonexistent" |
| 117 | " parent node %r" % (migration, parent) |
| 118 | ) |
| 119 | self.add_dummy_node(parent, migration, error_message) |
| 120 | self.node_map[child].add_parent(self.node_map[parent]) |
| 121 | self.node_map[parent].add_child(self.node_map[child]) |
no outgoing calls