Skip to content

Commit efa178d

Browse files
committed
Rename prefetch.Required to hints.Virtual
1 parent f7aad8a commit efa178d

File tree

8 files changed

+74
-78
lines changed

8 files changed

+74
-78
lines changed

django_virtual_models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
# Top-level imports:
1010
from .fields import *
1111
from .generic_views import *
12+
from .prefetch import hints
1213
from .serializers import *
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
from .hints import * # noqa

django_virtual_models/prefetch/hints.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ def wrapper(*args, **kwargs):
3636
raise MissingHintsException(
3737
f"Unexpected query happened inside `{decorated_func.__qualname__}`.\n"
3838
f"*Possible* line: {s.filename}:{s.lineno}\n"
39-
"Please check if all `prefetch.hints` are correct, "
39+
"Please check if all `hints` are correct, "
4040
"and update them and the virtual model as needed.\n"
4141
"Also, if this field is deferred on virtual model, "
42-
"it needs to be hinted with `prefech.Required`\n"
42+
"it needs to be hinted with `hints.Virtual`\n"
4343
"See more details about the query on the other exception above (in console)."
4444
) from exc
4545

@@ -124,7 +124,7 @@ class defined_on_virtual_model(OnOffDecorator): # noqa: N801
124124

125125

126126
@dataclass
127-
class Required:
127+
class Virtual:
128128
fields: List[str]
129129

130130
def __init__(self, *args):

django_virtual_models/prefetch/serializer_optimization.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import typing_extensions
1010

11-
from django_virtual_models import prefetch
11+
from django_virtual_models.prefetch import hints
1212

1313
from .. import utils
1414
from ..fields import BaseVirtualField, NestedJoin, NoOp
@@ -29,7 +29,7 @@ def _validate_type_hint(type_hint, invalid_type_hint_message):
2929
raise ImproperlyAnnotatedCodeException(invalid_type_hint_message)
3030

3131
metadata = typing_extensions.get_args(type_hint)[1:]
32-
metadata_only = [datum for datum in metadata if isinstance(datum, prefetch.Required)]
32+
metadata_only = [datum for datum in metadata if isinstance(datum, hints.Virtual)]
3333
if len(metadata_only) == 0:
3434
raise ImproperlyAnnotatedCodeException(invalid_type_hint_message)
3535
if len(metadata_only) > 1:
@@ -44,8 +44,8 @@ def _extract_type_hint_from_function(field, function):
4444
if len(type_hints_dict) == 0:
4545
raise ImproperlyAnnotatedCodeException(
4646
f"`{friendly_name}` inside `{field.parent.__class__.__name__}` "
47-
"must have a `prefetch.hints` decorator or "
48-
"a single `Annotated` type hint with a single `prefetch.Required` inside it."
47+
"must have a `hints` decorator or "
48+
"a single `Annotated` type hint with a single `hints.Virtual` inside it."
4949
)
5050
if len(type_hints_dict) > 1:
5151
raise ImproperlyAnnotatedCodeException(
@@ -58,14 +58,14 @@ def _extract_type_hint_from_function(field, function):
5858
# Check if Annotated is correct
5959
invalid_type_hint_message = (
6060
f"`{friendly_name}` inside `{field.parent.__class__.__name__}` "
61-
"must have a single `Annotated` type hint with a single `prefetch.Required` inside it."
61+
"must have a single `Annotated` type hint with a single `hints.Virtual` inside it."
6262
)
6363
_validate_type_hint(type_hint, invalid_type_hint_message=invalid_type_hint_message)
6464
return type_hint
6565

6666

6767
def _extract_type_hint_from_types_of_other_function(
68-
field: Field, decorator_instance: prefetch.hints.from_types_of
68+
field: Field, decorator_instance: hints.from_types_of
6969
):
7070
friendly_name = utils.get_friendly_field_name(field)
7171
typed_func = decorator_instance.typed_func
@@ -93,17 +93,15 @@ def _extract_type_hint_from_types_of_other_function(
9393
f"referenced by decorator `{decorator_instance.__class__.__name__}` "
9494
f"on `{friendly_name}` inside `{field.parent.__class__.__name__}` "
9595
f"must have a `Annotated` type hint on param `{decorator_instance.obj_param_name}` "
96-
"with a single `prefetch.Required` inside it."
96+
"with a single `hints.Virtual` inside it."
9797
)
9898
_validate_type_hint(type_hint, invalid_type_hint_message=invalid_type_hint_message)
9999
return type_hint
100100

101101

102102
def _extract_lookups_from_type_hint_obj(type_hint) -> List[str]:
103103
metadata = typing_extensions.get_args(type_hint)[1:]
104-
only = next(
105-
datum for datum in metadata if isinstance(datum, prefetch.Required)
106-
) # pragma: no cover
104+
only = next(datum for datum in metadata if isinstance(datum, hints.Virtual)) # pragma: no cover
107105
return only.fields
108106

109107

@@ -146,10 +144,10 @@ def _extract_lookups_from_function_type_hint(
146144
if decorator_instance is None:
147145
type_hint = _extract_type_hint_from_function(field, function)
148146
return _extract_lookups_from_type_hint_obj(type_hint)
149-
elif isinstance(decorator_instance, prefetch.hints.from_types_of):
147+
elif isinstance(decorator_instance, hints.from_types_of):
150148
type_hint = _extract_type_hint_from_types_of_other_function(field, decorator_instance)
151149
return _extract_lookups_from_type_hint_obj(type_hint)
152-
elif isinstance(decorator_instance, prefetch.hints.from_serializer):
150+
elif isinstance(decorator_instance, hints.from_serializer):
153151
serializer_instance = decorator_instance.serializer_cls(
154152
**(decorator_instance.serializer_kwargs or {})
155153
)
@@ -160,10 +158,10 @@ def _extract_lookups_from_function_type_hint(
160158
serializer_instance=serializer_instance,
161159
block_queries=block_queries,
162160
)
163-
elif isinstance(decorator_instance, prefetch.hints.defined_on_virtual_model):
161+
elif isinstance(decorator_instance, hints.defined_on_virtual_model):
164162
# `field.field_name` must be defined in virtual model
165163
return [field.field_name]
166-
elif isinstance(decorator_instance, prefetch.hints.no_deferred_fields):
164+
elif isinstance(decorator_instance, hints.no_deferred_fields):
167165
# `field.field_name` uses only fields that are always
168166
# fetched by the virtual model (which are all concrete fields minus the deferred ones)
169167
# so we don't need to return anything here to be included as a lookup
@@ -440,7 +438,7 @@ def recursively_find_lookup_list(
440438
f"Debug info `{k}` from `{self.serializer_instance.__class__.__name__}`"
441439
) # pragma: no cover
442440

443-
# activate the `prefetch.hints` decorators to block unexpected queries
441+
# activate the `hints` decorators to block unexpected queries
444442
if self.block_queries:
445443
self._activate_query_blocking(
446444
readable_serializer_fields=readable_serializer_fields,

docs/tutorial.md

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -435,54 +435,54 @@ class PersonSerializer(v.VirtualModelSerializer):
435435

436436
This serializer needs the `awards` field from `VirtualPerson` inside `get_has_won_any_award` method, otherwise it would raise an `AttributeError` as this is a *virtual field*. But how to specify that requirement? There are multiple ways of doing this:
437437

438-
#### Using type hints with `prefetch.Required`
438+
#### Using type hints with `hints.Virtual`
439439

440440
This library supports special type hints for informing what virtual fields a serializer method needs:
441441

442442
```python
443443
# Python < 3.9. For >= 3.9, use `from typing import Annotated`:
444444
from typing_extensions import Annotated
445-
from django_virtual_models import prefetch
445+
from django_virtual_models import hints
446446

447447
class PersonSerializer(v.VirtualModelSerializer):
448448
...
449449

450450
def get_has_won_any_award(
451451
self,
452452
# Type hint to ensure `awards` here:
453-
person: Annotated[Person, prefetch.Required("awards")]
453+
person: Annotated[Person, hints.Virtual("awards")]
454454
):
455455
return len(person.awards) > 0
456456
```
457457

458-
Now thanks to `prefetch.Required("awards")`, this serializer will be able to require the field `awards` from the virtual model. If necessary, you can pass multiple fields or even nested lookups: `prefetch.Required("awards", "nominations__movie")`.
458+
Now thanks to `hints.Virtual("awards")`, this serializer will be able to require the field `awards` from the virtual model. If necessary, you can pass multiple fields or even nested lookups: `hints.Virtual("awards", "nominations__movie")`.
459459

460460
!!! note
461-
`prefetch.Required` can be used to ensure the fetching of any `deferred_fields` a method needs without N+1s.
461+
`hints.Virtual` can be used to ensure the fetching of any `deferred_fields` a method needs without N+1s.
462462

463463
!!! note
464464
[Annotated](https://docs.python.org/3/library/typing.html#typing.Annotated) is a built-in Python construct that allows us to add custom annotations that don't affect type checking.
465465

466466
#### Using `from_types_of` decorator
467467

468-
If your method calls another function, you can add type hints to that function with `prefetch.Required` and then use the `from_types_of` decorator:
468+
If your method calls another function, you can add type hints to that function with `hints.Virtual` and then use the `from_types_of` decorator:
469469

470470
```python
471471
# Python < 3.9. For >= 3.9, use `from typing import Annotated`:
472472
from typing_extensions import Annotated
473-
from django_virtual_models import prefetch
473+
from django_virtual_models import hints
474474

475475
def check_person_has_won_any_award(
476476
# Type hint to ensure `awards` here:
477-
person: Annotated[Person, prefetch.Required("awards")]
477+
person: Annotated[Person, hints.Virtual("awards")]
478478
):
479479
return len(person.awards) > 0
480480

481481
class PersonSerializer(v.VirtualModelSerializer):
482482
...
483483

484484
# Decorator to specify where to find the type hint here:
485-
@prefetch.hints.from_types_of(check_person_has_won_any_award, "person")
485+
@hints.from_types_of(check_person_has_won_any_award, "person")
486486
def get_has_won_any_award(self, person, check_person_has_won_any_award):
487487
return check_person_has_won_any_award(person)
488488
```
@@ -494,12 +494,12 @@ Note the function `check_person_has_won_any_award` is received in the decorator
494494
If you have the `has_won_any_award` field in your virtual model, you can use the `defined_on_virtual_model` decorator:
495495

496496
```python
497-
from django_virtual_models import prefetch
497+
from django_virtual_models import hints
498498

499499
class PersonSerializer(v.VirtualModelSerializer):
500500
...
501501

502-
@prefetch.hints.defined_on_virtual_model() # <--- here
502+
@hints.defined_on_virtual_model() # <--- here
503503
def get_has_won_any_award(self, person):
504504
return person.has_won_any_award
505505
```
@@ -509,7 +509,7 @@ class PersonSerializer(v.VirtualModelSerializer):
509509
If your method field only uses concrete fields, in other words, it doesn't depend on any virtual model fields, nor any `deferred_fields`, you can use the `no_deferred_fields` decorator:
510510

511511
```python
512-
from django_virtual_models import prefetch
512+
from django_virtual_models import hints
513513

514514
class PersonSerializer(v.VirtualModelSerializer):
515515
name_title_case = serializers.SerializerMethodField()
@@ -519,7 +519,7 @@ class PersonSerializer(v.VirtualModelSerializer):
519519
virtual_model = VirtualPerson
520520
fields = ["name_title_case"]
521521

522-
@prefetch.hints.no_deferred_fields() # <--- here
522+
@hints.no_deferred_fields() # <--- here
523523
def get_name_title_case(self, person):
524524
return person.name.title()
525525
```
@@ -529,18 +529,18 @@ class PersonSerializer(v.VirtualModelSerializer):
529529
DRF Serializers can use directly model properties and methods in `Meta.fields`. If your serializer uses a model property or method, you need to use the same hints described by the previous section ["Prefetching for method fields"](#prefetching-for-method-fields-serializermethodfield). For example:
530530

531531
```python
532-
from django_virtual_models import prefetch
532+
from django_virtual_models import hints
533533

534534
class Person(models.Model):
535535
name = models.CharField(max_length=255)
536536

537537
@property # <--- property must come before
538-
@prefetch.hints.no_deferred_fields()
538+
@hints.no_deferred_fields()
539539
def name_title_case(self):
540540
return self.name.title()
541541

542542
# method w/o params, DRF supports that too:
543-
def has_won_any_award(self: Annotated[Person, prefetch.Required("awards")]):
543+
def has_won_any_award(self: Annotated[Person, hints.Virtual("awards")]):
544544
return len(person.awards) > 0
545545

546546
class PersonSerializer(v.VirtualModelSerializer):

0 commit comments

Comments
 (0)