Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce the concept of "producers" #43

Merged
merged 11 commits into from
Jul 16, 2021
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ from datetime import datetime
def produce_age(instance):
return datetime.now().year - instance.birth_year

project_age = projectors.wrap_producer("age", produce_age)
project_age = projectors.producer_to_projector("age", produce_age)

author = Author(name="Some Author", birth_year=1984)
print(project_age(author))
Expand All @@ -129,7 +129,7 @@ A dictionary is returned because these projectors are intended to be _composable
from django_readers import producers, projectors

project = projectors.combine(
projectors.wrap_producer("name", producers.attr("name")),
projectors.producer_to_projector("name", producers.attr("name")),
project_age,
)
print(project(author))
Expand All @@ -142,11 +142,11 @@ Related objects can also be produced using the `producers.relationship` function

```python
project = projectors.combine(
projectors.wrap_producer("name", producers.attr("name")),
projectors.producer_to_projector("name", producers.attr("name")),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this extremely verbose but if its internal only I guess its better to be explicitly very explicit and explicit and verbose and extra verbosely explicit

project_age,
projectors.wrap_producer("book_set", producers.relationship("book_set", projectors.combine(
projectors.wrap_producer(producers.attr("title")),
projectors.wrap_producer(producers.attr("publication_year"),
projectors.producer_to_projector("book_set", producers.relationship("book_set", projectors.combine(
projectors.producer_to_projector(producers.attr("title")),
projectors.producer_to_projector(producers.attr("publication_year"),
)),
)
print(project(author))
Expand All @@ -163,7 +163,7 @@ By default, the `transform_value` function is only called if the value of the at

Finally, the `producers.method` function will call the given method name on the instance, returning the result under a key matching the method name. Any extra arguments passed to `producers.method` will be passed along to the method.

### `django_readers.producer_pairs` and `django_readers.projector_pairs`: "reader pairs" combining `prepare` with `produce` and `project`
### `django_readers.pairs`: "reader pairs" combining `prepare` with `produce` and `project`

`prepare` and `produce` (and therefore also `project`) functions are intimately connected, with the `produce`/`project` functions usually depending on fields, annotations or relationships loaded by the `prepare` function. For this reason, `django-readers` expects these functions to live together in two-tuples: `(prepare, produce)` (a "producer pair") and ``(prepare, project)` (a "projector pair"). Remember that the difference between `produce` and `project` is that the former returns a single value, whereas the latter returns a dictionary binding one or more names (keys) to one or more values.

Expand All @@ -189,24 +189,24 @@ print(project(author))

The `pairs.field` function takes the same `transform_value` and `transform_value_if_none` arguments as `producers.attr` (see above).

When composing multiple pairs together, it is again necessary to wrap the producer to convert it to a projector, thus forming `(prepare, project)` pairs. This can be done with the `pairs.wrap_producer` function:
When composing multiple pairs together, it is again necessary to wrap the producer to convert it to a projector, thus forming `(prepare, project)` pairs. This can be done with the `pairs.producer_to_projector` function:

```python
prepare, project = pairs.combine(
pairs.wrap_producer("name", pairs.field("name")),
pairs.wrap_producer("birth_year", pairs.field("birth_year")),
pairs.producer_to_projector("name", pairs.field("name")),
pairs.producer_to_projector("birth_year", pairs.field("birth_year")),
)
```

Relationships can automatically be loaded and projected, too:

```python
prepare, project = pairs.combine(
pairs.wrap_producer("name", pairs.field("name")),
pairs.producer_to_projector("name", pairs.field("name")),
age_pair,
pairs.wrap_producer("book_set", pairs.relationship("book_set", pairs.combine(
pairs.wrap_producer("title", pairs.field("title")),
pairs.wrap_producer("publication_year", pairs.field("publication_year")),
pairs.producer_to_projector("book_set", pairs.relationship("book_set", pairs.combine(
pairs.producer_to_projector("title", pairs.field("title")),
pairs.producer_to_projector("publication_year", pairs.field("publication_year")),
)))
)
```
Expand All @@ -223,15 +223,15 @@ As a shortcut, the `pairs` module provides functions called `filter`, `exclude`

```python
prepare, project = pairs.combine(
pairs.wrap_producer("name", pairs.field("name")),
pairs.producer_to_projector("name", pairs.field("name")),
age_pair,
pairs.wrap_producer("book_set", pairs.relationship(
pairs.producer_to_projector("book_set", pairs.relationship(
"book_set",
pairs.combine(
pairs.filter(publication_year__gte=2020),
pairs.order_by("title"),
pairs.wrap_producer("title", pairs.field("title")),
pairs.wrap_producer("publication_year", pairs.field("publication_year")),
pairs.producer_to_projector("title", pairs.field("title")),
pairs.producer_to_projector("publication_year", pairs.field("publication_year")),
),
to_attr="recent_books"
))
Expand Down
4 changes: 2 additions & 2 deletions django_readers/pairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from django_readers import producers, projectors, qs


def wrap_producer(name, pair):
def producer_to_projector(name, pair):
prepare, produce = pair
return prepare, projectors.wrap_producer(name, produce)
return prepare, projectors.producer_to_projector(name, produce)


def field(name, *, transform_value=None, transform_value_if_none=False):
Expand Down
2 changes: 1 addition & 1 deletion django_readers/projectors.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
def wrap_producer(key, producer):
def producer_to_projector(key, producer):
def projector(instance):
return {key: producer(instance)}

Expand Down
14 changes: 8 additions & 6 deletions django_readers/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,23 @@ def process(spec):


def relationship(name, relationship_spec, to_attr=None):
return pairs.wrap_producer(
return pairs.producer_to_projector(
to_attr or name, pairs.relationship(name, process(relationship_spec), to_attr)
)


def relationship_or_wrap(name, child_spec):
if isinstance(child_spec, str):
producer = pairs.field(child_spec)
producer_pair = pairs.field(child_spec)
elif isinstance(child_spec, list):
producer = pairs.relationship(name, process(child_spec))
producer_pair = pairs.relationship(name, process(child_spec))
elif isinstance(child_spec, dict):
if len(child_spec) != 1:
raise ValueError("Aliased relationship spec must contain only one key")
relationship_name, relationship_spec = next(iter(child_spec.items()))
producer = pairs.relationship(relationship_name, process(relationship_spec))
producer_pair = pairs.relationship(
relationship_name, process(relationship_spec)
)
else:
producer = child_spec
return pairs.wrap_producer(name, producer)
producer_pair = child_spec
return pairs.producer_to_projector(name, producer_pair)
Loading