mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-01 00:17:40 +03:00 
			
		
		
		
	* Add support for Python 3.13 * Fix extracting tox env with -dev Python versions * Fix view description inspection in Python 3.13 Python 3.13 introduced docstrings for None: https://github.com/python/cpython/pull/117813 In Python 3.12, this is an empty string: ``` ➜ python3.12 Python 3.12.6 (main, Sep 10 2024, 19:06:17) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> d = None >>> d.__doc__ >>> ``` In Python 3.13, it's no longer empty: ``` ➜ python3.13 Python 3.13.0rc2+ (heads/3.13:660baa1, Sep 10 2024, 18:57:50) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> d = None >>> d.__doc__ 'The type of the None singleton.' >>> ``` Adding a check in the inspector that get the view description out the view function docstring to catch this edge case.
		
			
				
	
	
		
			127 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			127 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| """
 | |
| inspectors.py   # Per-endpoint view introspection
 | |
| 
 | |
| See schemas.__init__.py for package overview.
 | |
| """
 | |
| import re
 | |
| from weakref import WeakKeyDictionary
 | |
| 
 | |
| from django.utils.encoding import smart_str
 | |
| 
 | |
| from rest_framework.settings import api_settings
 | |
| from rest_framework.utils import formatting
 | |
| 
 | |
| 
 | |
| class ViewInspector:
 | |
|     """
 | |
|     Descriptor class on APIView.
 | |
| 
 | |
|     Provide subclass for per-view schema generation
 | |
|     """
 | |
| 
 | |
|     # Used in _get_description_section()
 | |
|     header_regex = re.compile('^[a-zA-Z][0-9A-Za-z_]*:')
 | |
| 
 | |
|     def __init__(self):
 | |
|         self.instance_schemas = WeakKeyDictionary()
 | |
| 
 | |
|     def __get__(self, instance, owner):
 | |
|         """
 | |
|         Enables `ViewInspector` as a Python _Descriptor_.
 | |
| 
 | |
|         This is how `view.schema` knows about `view`.
 | |
| 
 | |
|         `__get__` is called when the descriptor is accessed on the owner.
 | |
|         (That will be when view.schema is called in our case.)
 | |
| 
 | |
|         `owner` is always the owner class. (An APIView, or subclass for us.)
 | |
|         `instance` is the view instance or `None` if accessed from the class,
 | |
|         rather than an instance.
 | |
| 
 | |
|         See: https://docs.python.org/3/howto/descriptor.html for info on
 | |
|         descriptor usage.
 | |
|         """
 | |
|         if instance in self.instance_schemas:
 | |
|             return self.instance_schemas[instance]
 | |
| 
 | |
|         self.view = instance
 | |
|         return self
 | |
| 
 | |
|     def __set__(self, instance, other):
 | |
|         self.instance_schemas[instance] = other
 | |
|         if other is not None:
 | |
|             other.view = instance
 | |
| 
 | |
|     @property
 | |
|     def view(self):
 | |
|         """View property."""
 | |
|         assert self._view is not None, (
 | |
|             "Schema generation REQUIRES a view instance. (Hint: you accessed "
 | |
|             "`schema` from the view class rather than an instance.)"
 | |
|         )
 | |
|         return self._view
 | |
| 
 | |
|     @view.setter
 | |
|     def view(self, value):
 | |
|         self._view = value
 | |
| 
 | |
|     @view.deleter
 | |
|     def view(self):
 | |
|         self._view = None
 | |
| 
 | |
|     def get_description(self, path, method):
 | |
|         """
 | |
|         Determine a path description.
 | |
| 
 | |
|         This will be based on the method docstring if one exists,
 | |
|         or else the class docstring.
 | |
|         """
 | |
|         view = self.view
 | |
| 
 | |
|         method_name = getattr(view, 'action', method.lower())
 | |
|         method_func = getattr(view, method_name, None)
 | |
|         method_docstring = method_func.__doc__
 | |
|         if method_func and method_docstring:
 | |
|             # An explicit docstring on the method or action.
 | |
|             return self._get_description_section(view, method.lower(), formatting.dedent(smart_str(method_docstring)))
 | |
|         else:
 | |
|             return self._get_description_section(view, getattr(view, 'action', method.lower()),
 | |
|                                                  view.get_view_description())
 | |
| 
 | |
|     def _get_description_section(self, view, header, description):
 | |
|         lines = description.splitlines()
 | |
|         current_section = ''
 | |
|         sections = {'': ''}
 | |
| 
 | |
|         for line in lines:
 | |
|             if self.header_regex.match(line):
 | |
|                 current_section, separator, lead = line.partition(':')
 | |
|                 sections[current_section] = lead.strip()
 | |
|             else:
 | |
|                 sections[current_section] += '\n' + line
 | |
| 
 | |
|         # TODO: SCHEMA_COERCE_METHOD_NAMES appears here and in `SchemaGenerator.get_keys`
 | |
|         coerce_method_names = api_settings.SCHEMA_COERCE_METHOD_NAMES
 | |
|         if header in sections:
 | |
|             return sections[header].strip()
 | |
|         if header in coerce_method_names:
 | |
|             if coerce_method_names[header] in sections:
 | |
|                 return sections[coerce_method_names[header]].strip()
 | |
|         return sections[''].strip()
 | |
| 
 | |
| 
 | |
| class DefaultSchema(ViewInspector):
 | |
|     """Allows overriding AutoSchema using DEFAULT_SCHEMA_CLASS setting"""
 | |
|     def __get__(self, instance, owner):
 | |
|         result = super().__get__(instance, owner)
 | |
|         if not isinstance(result, DefaultSchema):
 | |
|             return result
 | |
| 
 | |
|         inspector_class = api_settings.DEFAULT_SCHEMA_CLASS
 | |
|         assert issubclass(inspector_class, ViewInspector), (
 | |
|             "DEFAULT_SCHEMA_CLASS must be set to a ViewInspector (usually an AutoSchema) subclass"
 | |
|         )
 | |
|         inspector = inspector_class()
 | |
|         inspector.view = instance
 | |
|         return inspector
 |