Skip to content

Commit

Permalink
Wordsmith
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Aug 9, 2023
1 parent 2d3cd0a commit a6845dc
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 29 deletions.
33 changes: 18 additions & 15 deletions docs/core-concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ You will probably use some framework integration and not the low-level API direc

## Registries

A **{class}`svcs.Registry`** allows to register factories for types.
It's expected to live as long as your application lives.
A **{class}`svcs.Registry`** allows registering factories for types.
A registry should live as long as your application lives.
Its only job is to store and retrieve factories along with some metadata.

It is possible to register either factory callables or values:
Expand Down Expand Up @@ -105,7 +105,7 @@ How to achieve this in other frameworks elegantly is TBD.

### Cleanup

If a factory is a [generator](https://docs.python.org/3/tutorial/classes.html#generators) and *yields* the instance instead of returning it, the generator will be remembered by the container.
If a factory is a [generator](https://docs.python.org/3/tutorial/classes.html#generators) and *yields* the instance instead of returning it, the container will remember the generator.
At the end, you run {meth}`svcs.Container.close()` and all generators will be finished (i.e. called `next(factory)` again).
You can use this to close files, return database connections to a pool, et cetera.

Expand All @@ -124,13 +124,13 @@ You can also use containers as (async) context managers that (a)close automatica
Cleaned up!
```

Failing cleanups are logged at `warning` level but otherwise ignored.
Failing cleanups are logged at warning level but otherwise ignored.

::: {important}
The key idea is that your business code doesn't have to care about cleaning up services it has requested.
:::

That makes it even easier to test because the business codes makes fewer assumptions about the object it's getting.
That makes testing even easier because the business code makes fewer assumptions about the object it's getting.

(health)=

Expand All @@ -140,17 +140,20 @@ Each registered service may have a `ping` callable that you can use for health c
You can request all pingable registered services with {meth}`svcs.Container.get_pings()`.
This returns a list of {class}`svcs.ServicePing` objects that currently have a name property to identify the ping and a `ping` method that instantiates the service, adds it to the cleanup list, and runs the ping.

If you have async services (either factory or ping callable), you can use `aping()` instead.
`aping()` works with sync services too, so you can use it universally in async code.
If you have async services (factory or ping callable), you can use `aping()` instead.
`aping()` works with sync services, too, so you can use it universally in async code.
You can look at the `is_async` property to check whether you *need* to use `aget()`, though.

Here's an example for a health check endpoint in Pyramid[^flask]:

[^flask]: See the [Flask integration](flask.md) chapter for a Flask equivalent.
Here's how a health check endpoint could look in Flask or Pyramid:

::: {tab} Flask
```{literalinclude} examples/health_check_flask.py
```
:::
::: {tab} Pyramid
```{literalinclude} examples/health_check_pyramid.py
```

:::

## Life Cycle Summary

Expand All @@ -159,21 +162,21 @@ On the other hand, the {class}`svcs.Container` object should live on a request-s


::: {important}
The core APIs only use vanilla objects without any global state but also without any comfort.
The core APIs only use vanilla objects without any global state but also without any comfort.
It gets more interesting when using framework-specific integrations where the life cycle of the container and, thus, services is handled automatically.
:::


## Debugging Registrations

If you end up being confused where a particular factory for a type has been defined, *svcs* logs every registration at debug level along with a stack trace.
If you are confused about where a particular factory for a type has been defined, *svcs* logs every registration at debug level along with a stack trace.

Set the *svcs* logger to `DEBUG` to see them:

```{literalinclude} examples/debugging_with_logging.py
```

Gives you an output like this:
It gives you an output like this:

```text
svcs: registered factory <built-in method now of type object at 0x103468980> for service type datetime.datetime
Expand All @@ -190,7 +193,7 @@ Stack (most recent call last):
log.debug(
```

You can see that the datetime factory and the str value have been both registered in `debugging_with_logging.py`, down to the line number.
You can see that the datetime factory and the str value have both been registered in `debugging_with_logging.py`, down to the line number.


## API Reference
Expand Down
2 changes: 1 addition & 1 deletion docs/why.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def cleanup():
```

The generator-based setup and cleanup may remind you of [*pytest* fixtures](https://docs.pytest.org/en/stable/explanation/fixtures.html).
The hooks that are defined as `on_registry_close` are called when you call `Registry.close()` – e.g. when your application is shutting down.
The callbacks that are defined as `on_registry_close` are called when you call `Registry.close()` – e.g. when your application is shutting down.

Next, if you've registered health checks (called *pings*) for your services, you can write a simple health check endpoint.
This is how it could look in Flask or Pyramid:
Expand Down
16 changes: 8 additions & 8 deletions src/svcs/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,8 @@ class Registry:
An instance of this should live as long as your application does.
Also works as a context manager that runs ``on_registry_close`` hooks on
exit:
Also works as a context manager that runs ``on_registry_close`` callbacks
on exit:
.. doctest::
Expand Down Expand Up @@ -179,7 +179,7 @@ def register_factory(
Register *factory* to be used when asked for a *svc_type*.
Repeated registrations overwrite previous ones, but the
*on_registry_close* hooks are run all together when the registry is
*on_registry_close* callbacks are run all together when the registry is
closed.
Args:
Expand Down Expand Up @@ -284,9 +284,9 @@ def get_registered_service_for(self, svc_type: type) -> RegisteredService:

def close(self) -> None:
"""
Clear registrations and run synchronous *on_registry_close* hooks.
Clear registrations and run synchronous *on_registry_close* callbacks.
Async hooks are *not* awaited and a warning is raised
Async callbacks are *not* awaited and a warning is raised
Errors are logged at warning level, but otherwise ignored.
"""
Expand All @@ -307,7 +307,7 @@ def close(self) -> None:
log.debug("closed %r", name)
except Exception: # noqa: BLE001
log.warning(
"Registry's on_registry_close hook failed for %r.",
"Registry's on_registry_close callback failed for %r.",
name,
exc_info=True,
extra={"svcs_service_name": name},
Expand All @@ -318,7 +318,7 @@ def close(self) -> None:

async def aclose(self) -> None:
"""
Clear registrations and run all *on_registry_close* hooks.
Clear registrations and run all *on_registry_close* callbacks.
Errors are logged at warning level, but otherwise ignored.
Expand All @@ -340,7 +340,7 @@ async def aclose(self) -> None:
log.debug("closed %r", name)
except Exception: # noqa: BLE001, PERF203
log.warning(
"Registry's on_registry_close hook failed for %r.",
"Registry's on_registry_close callback failed for %r.",
name,
exc_info=True,
extra={"svcs_service_name": name},
Expand Down
4 changes: 2 additions & 2 deletions tests/test_pyramid.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def test_close_nop():

def test_close(config):
"""
Closing a config with svcs_registry calls on_registry_close hooks on the
registered svcs.pyramid.
Closing a config with svcs_registry calls on_registry_close callbacks on
the registered svcs.pyramid.
"""
orc = Mock()

Expand Down
6 changes: 3 additions & 3 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ def test_close_warns_about_async(self, registry):
Calling close raises a warning if there are async cleanups.
"""

async def hook():
async def callback():
...

registry.register_factory(Service, Service, on_registry_close=hook)
registry.register_factory(Service, Service, on_registry_close=callback)

with pytest.warns(
UserWarning,
Expand Down Expand Up @@ -145,7 +145,7 @@ def test_close_logs_failures(self, registry, caplog):
...

assert [
"Registry's on_registry_close hook failed for 'tests.ifaces.Service'."
"Registry's on_registry_close callback failed for 'tests.ifaces.Service'."
] == caplog.messages
assert "tests.ifaces.Service" == caplog.records[0].svcs_service_name

Expand Down

0 comments on commit a6845dc

Please sign in to comment.