| 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. |