|
1 | 1 | import inspect |
| 2 | +import json |
2 | 3 | import sys |
3 | 4 | from abc import ABCMeta |
4 | 5 | from collections import defaultdict |
5 | 6 | from collections.abc import Hashable |
6 | 7 | from typing import List, Type, Optional, TypeVar, Union, Generic |
7 | 8 |
|
| 9 | +import inflection |
8 | 10 | from django import __version__ as DJANGO_VERSION |
9 | 11 | from django.utils.module_loading import import_string |
10 | 12 | from rest_framework import fields, serializers |
@@ -285,6 +287,10 @@ def build(self, extra_components) -> dict: |
285 | 287 | for extra_type, extra_component_dict in extra_components.items(): |
286 | 288 | for component_name, component_schema in extra_component_dict.items(): |
287 | 289 | output[extra_type][component_name] = component_schema |
| 290 | + |
| 291 | + if 'schemas' in output: |
| 292 | + postprocess_schema_enums(output['schemas']) |
| 293 | + |
288 | 294 | # sort by component type then by name |
289 | 295 | return { |
290 | 296 | type: {name: output[type][name] for name in output[type].keys()} |
@@ -329,3 +335,37 @@ def get_match(cls, target) -> Optional[T]: |
329 | 335 | if extension._matches(target): |
330 | 336 | return extension(target) |
331 | 337 | return None |
| 338 | + |
| 339 | + |
| 340 | +def postprocess_schema_enums(schemas): |
| 341 | + """ |
| 342 | + simple replacement of Enum/Choices that globally share the same name and have |
| 343 | + the same choices. Aids client generation to not generate a separate enum for |
| 344 | + every occurrence. only takes effect when replacement is guaranteed to be correct. |
| 345 | + """ |
| 346 | + hash_mapping = defaultdict(set) |
| 347 | + # collect all enums, their names and contents |
| 348 | + for schema in schemas.values(): |
| 349 | + for prop_name, prop_schema in schema.get('properties', {}).items(): |
| 350 | + if 'enum' not in prop_schema: |
| 351 | + continue |
| 352 | + hash_mapping[prop_name].add( |
| 353 | + hash(json.dumps(prop_schema, sort_keys=True)) |
| 354 | + ) |
| 355 | + # safe replacement requires name to have only one set of enum values |
| 356 | + candidate_enums = { |
| 357 | + prop_name for prop_name, prop_hash_set in hash_mapping.items() |
| 358 | + if len(prop_hash_set) == 1 |
| 359 | + } |
| 360 | + # replace all valid occurrences with enum schema component |
| 361 | + for schema_name in list(schemas): |
| 362 | + for prop_name, prop_schema in schemas[schema_name].get('properties', {}).items(): |
| 363 | + if 'enum' not in prop_schema: |
| 364 | + continue |
| 365 | + if prop_name not in candidate_enums: |
| 366 | + continue |
| 367 | + enum_name = f'{inflection.camelize(prop_name)}Enum' |
| 368 | + schemas[enum_name] = prop_schema |
| 369 | + schemas[schema_name]['properties'][prop_name] = { |
| 370 | + '$ref': f'#/components/schemas/{enum_name}' |
| 371 | + } |
0 commit comments