| | 259 | |
| | 260 | ==== Used internal caching instead of lru_cache |
| | 261 | Our first approach to caching was to use functools.lru_cache. lru_cache is a simple decorator that provides cache and an expiry function |
| | 262 | built-it. It worked correctly with the new API but cProfile quickly showed how a lot of computing time was done inside lru_cache itself. |
| | 263 | |
| | 264 | The decision taken was to do very caching with simple try / catch and a dictionary for memoizing. This is also because we really don't need |
| | 265 | the 'lru' part of 'lru_caching': there are only a finite number of combinations that can be called. |
| | 266 | |
| | 267 | ==== Used internal caching instead of lru_cache |
| | 268 | Our first approach to caching was to use functools.lru_cache. lru_cache is a simple decorator that provides cache and an expiry function |
| | 269 | built-it. It worked correctly with the new API but cProfile quickly showed how a lot of computing time was done inside lru_cache itself. |
| | 270 | |
| | 271 | The decision taken was to do very caching with simple try / catch and a dictionary for memoizing. This is also because we really don't need |
| | 272 | the 'lru' part of 'lru_caching': there are only a finite number of combinations that can be called. |
| | 273 | |
| | 274 | ==== Use cached_properties when possible |
| | 275 | Function calls are expensive in Python, All sensible attributes with no arguments have been transformed into cached_properties. |
| | 276 | A cached property is a read-only property that is calculated on demand and automatically cached. If the value has already been calculated, |
| | 277 | the cached value is returned. Cached properties avoid a new stack and are used for fast-access to fields, concrete_fields, |
| | 278 | local_concrete_fields, many_to_many, field_names |
| | 279 | |
| | 280 | ==== enabled m2m fields by default on get_field |
| | 281 | The old get_field API was defined as follows: |
| | 282 | |
| | 283 | {{{ |
| | 284 | def get_fields(self, field_name, many_to_many=True) |
| | 285 | }}} |
| | 286 | |
| | 287 | Our first iteration of the API was to refactor this as |
| | 288 | |
| | 289 | {{{ |
| | 290 | def get_fields(self, field_name, include_related=True) |
| | 291 | }}} |
| | 292 | |
| | 293 | This was done for 2 reasons: |
| | 294 | 1) We managed to squash 2 functions (get_field and get_field_by_name) in 1 single call |
| | 295 | 2) I could not find any reason for the many_to_many flag to exist! there can never be data and m2m fields with the same name. So this looked |
| | 296 | like a legacy parameter that didn't have any effect (because turning it off did not break any tests) |
| | 297 | |
| | 298 | Finally, the reason the many_to_many flag existed was for a special validation case that was not documented anywhere. Russell helped me in |
| | 299 | looking for edge cases and finally I came up with a failing test case: https://github.com/django/django/pull/2893. The test case would fail on the |
| | 300 | new API but succeed on master. |
| | 301 | |
| | 302 | Our final iteration was to add all the field types as flags to get_field. By making m2m as first parameter, we avoid breaking existing implementations |
| | 303 | and maintain a similarity with the 'get_fields' API. |