| | 187 | def orderClassesByDependencyLevel(self, classes): |
|---|
| | 188 | """ |
|---|
| | 189 | Return classes ordered by their depth in the class dependency |
|---|
| | 190 | tree (this is *not* the inheritance tree), from the |
|---|
| | 191 | top level (independant) classes to the deepest level. |
|---|
| | 192 | The dependency tree is defined by the foreign key relations. |
|---|
| | 193 | """ |
|---|
| | 194 | # @@: written as a self-contained function for now, to prevent |
|---|
| | 195 | # having to modify any core SQLObject component and namespace |
|---|
| | 196 | # contamination. |
|---|
| | 197 | # yemartin - 2006-08-08 |
|---|
| | 198 | |
|---|
| | 199 | class SQLObjectCircularReferenceError(Exception): pass |
|---|
| | 200 | |
|---|
| | 201 | def findReverseDependencies(cls): |
|---|
| | 202 | """ |
|---|
| | 203 | Return a list of classes that cls depends on. Note that |
|---|
| | 204 | "depends on" here mean "has a foreign key pointing to". |
|---|
| | 205 | """ |
|---|
| | 206 | depended = [] |
|---|
| | 207 | for col in cls.sqlmeta.columnList: |
|---|
| | 208 | if col.foreignKey: |
|---|
| | 209 | other = findClass(col.foreignKey, |
|---|
| | 210 | col.soClass.sqlmeta.registry) |
|---|
| | 211 | if other not in depended: |
|---|
| | 212 | depended.append(other) |
|---|
| | 213 | return depended |
|---|
| | 214 | |
|---|
| | 215 | # Cache to save already calculated dependency levels. |
|---|
| | 216 | dependency_levels = {} |
|---|
| | 217 | def calculateDependencyLevel(cls, dependency_stack=[]): |
|---|
| | 218 | """ |
|---|
| | 219 | Recursively calculate the dependency level of cls, while |
|---|
| | 220 | using the dependency_stack to detect any circular reference. |
|---|
| | 221 | """ |
|---|
| | 222 | # Return value from the cache if already calculated |
|---|
| | 223 | if dependency_levels.has_key(cls): |
|---|
| | 224 | return dependency_levels[cls] |
|---|
| | 225 | # Check for circular references |
|---|
| | 226 | if cls in dependency_stack: |
|---|
| | 227 | dependency_stack.append(cls) |
|---|
| | 228 | raise SQLObjectCircularReferenceError, ( |
|---|
| | 229 | "Found a circular reference: %s " % |
|---|
| | 230 | (' --> '.join([x.__name__ |
|---|
| | 231 | for x in dependency_stack]))) |
|---|
| | 232 | dependency_stack.append(cls) |
|---|
| | 233 | # Recursively inspect dependent classes. |
|---|
| | 234 | depended = findReverseDependencies(cls) |
|---|
| | 235 | if depended: |
|---|
| | 236 | level = max([calculateDependencyLevel(x, dependency_stack) |
|---|
| | 237 | for x in depended]) + 1 |
|---|
| | 238 | else: |
|---|
| | 239 | level = 0 |
|---|
| | 240 | dependency_levels[cls] = level |
|---|
| | 241 | return level |
|---|
| | 242 | |
|---|
| | 243 | # Now simply calculate and sort by dependency levels: |
|---|
| | 244 | try: |
|---|
| | 245 | sorter = [] |
|---|
| | 246 | for cls in classes: |
|---|
| | 247 | level = calculateDependencyLevel(cls) |
|---|
| | 248 | sorter.append((level, cls)) |
|---|
| | 249 | sorter.sort() |
|---|
| | 250 | ordered_classes = [cls for level, cls in sorter] |
|---|
| | 251 | except SQLObjectCircularReferenceError, msg: |
|---|
| | 252 | # Failsafe: return the classes as-is if a circular reference |
|---|
| | 253 | # prevented the dependency levels to be calculated. |
|---|
| | 254 | print ("Warning: a circular reference was detected in the " |
|---|
| | 255 | "model. Unable to sort the classes by dependency: they " |
|---|
| | 256 | "will be treated in alphabetic order. This may or may " |
|---|
| | 257 | "not work depending on your database backend. " |
|---|
| | 258 | "The error was:\n%s" % msg) |
|---|
| | 259 | return classes |
|---|
| | 260 | return ordered_classes |
|---|
| | 261 | |
|---|