Skip to content

Commit 824abb8

Browse files
committed
docs: Remove Slothy from public APIs page
1 parent 29b1118 commit 824abb8

1 file changed

Lines changed: 13 additions & 15 deletions

File tree

docs/guide/users/recommendations/public-apis.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Other components *could* be considered for the public API, but usually require t
3232

3333
Besides, logging and exception messages simply cannot allow deprecation periods where both old and new messages are emitted. Maintainers could however consider adding unique, short codes to messages for more robust consumption.
3434

35-
> GRIFFE: **Our recommendation — Communicate your public API, verify what you can.**
35+
> GRIFFE: **Our recommendation — Communicate your public API, verify what you can.**
3636
> Take the time to learn about and use ways to declare, communicate and deprecate your public API. Your users will have an easier time using your library. On the maintenance side, you won't get bug reports for uses that are not supported, or you will be able to quickly close them by pointing at the documentation explaining what your public API is, or why something was deprecated, for how long, and how to upgrade.
3737
>
3838
> Automate verifications around your public API with tools like Griffe. Currently Griffe doesn't support checking CLI configuration options, logger names or raised exceptions. If you have the capacity to, verify these manually before each release. [Griffe checks](../checking.md) and [API rules enforcement](#ensuring-api-rules) are a very good starting point.
@@ -123,7 +123,7 @@ Note that the wildcard imports logic stays the same, and imports either all obje
123123

124124
---
125125

126-
> GRIFFE: **Our recommendation — Use the underscore prefix and `__all__` conventions.**
126+
> GRIFFE: **Our recommendation — Use the underscore prefix and `__all__` conventions.**
127127
> Use both the underscore prefix convention for consistent naming at module and class levels, and the `__all__` convention for declaring your public API. We do not recommend using the redundant aliases convention, because it doesn't provide any information at runtime. We do not recommend the wildcard import convention either, for the same reason and [for additional reasons mentioned here](python-code.md#avoid-wildcard-imports). We still provide the [`griffe-public-redundant-aliases`](https://mkdocstrings.github.io/griffe-public-redundant-aliases/) and [`griffe-public-wildcard-imports`](https://mkdocstrings.github.io/griffe-public-wildcard-imports/) extensions for those who would still like to rely on these conventions.
128128
>
129129
> Our recommendation matches [PEP 8](https://peps.python.org/pep-0008/#public-and-internal-interfaces):
@@ -132,7 +132,7 @@ Note that the wildcard imports logic stays the same, and imports either all obje
132132
>
133133
> > Even with `__all__` set appropriately, internal interfaces (packages, modules, classes, functions, attributes or other names) should still be prefixed with a single leading underscore.
134134
135-
> TIP: **Concatenating `__all__` for easier maintenance of `__init__` modules.**
135+
> TIP: **Concatenating `__all__` for easier maintenance of `__init__` modules.**
136136
> If you worry about maintenance of your `__init__` modules, know that you can very well concatenate `__all__` lists from submodules into the current one:
137137
>
138138
> ```tree
@@ -168,7 +168,7 @@ Note that the wildcard imports logic stays the same, and imports either all obje
168168
>
169169
> # Griffe supports the `+` and `+=` operators too:
170170
> # __all__ = ["this"] + subpackage1_all + subpackage2_all
171-
> # __all__ = ["this"]; __all__ += subpackage1_all; __all__ += subpackage2_all
171+
> # __all__ = ["this"]; __all__ += subpackage1_all; __all__ += subpackage2_all
172172
> ```
173173
>
174174
> However we would argue that `this1a`, `that1a`, `this2a` and `that2a` should not be exposed publicly in more than one location. See our section on [unique names and public locations](#unique-names-and-public-locations).
@@ -279,10 +279,10 @@ If you don't want to bother prefixing every module with an underscore, you could
279279
280280
Whatever *hidden* layout you choose (private modules, internals, private package), it is not very important, as you will be able to switch from one to another easily. In Griffe we chose to experiment and go with the private package approach. This highlighted a few shortcomings that we were able to address in both Griffe and mkdocstrings-python. We later moved the private package under an internal folder.
281281
282-
WARNING: **Top-level-only exposition doesn't play well with large packages.**
282+
WARNING: **Top-level-only exposition doesn't play well with large packages.**
283283
The *fully* hidden layout plays well with small to medium projects. If you maintain a large project, it can become very impractical for both you and your users to expose every single object in the top-level `__init__` module. For large projects, it therefore makes sense to keep at least one or two additional public layers in your module layout. Sometimes packages also implement many variations of the same abstract class, using the same name in many different modules: in these cases, the modules are effective namespaces that could be kept in the public API.
284284
285-
GRIFFE: **Our recommendation — Hide your module layout early.**
285+
GRIFFE: **Our recommendation — Hide your module layout early.**
286286
Start hiding your module layout early! It is much easier to (partially) expose the layout later than to hide it after your users started relying on it. It will also make code reorganizations much easier.
287287
288288
## Unique names and public locations
@@ -349,7 +349,7 @@ my_package/
349349

350350
It feels weird to "unpublicize" the `Hello` class in `my_package.module` by declaring an empty `__all__`, so maybe the module should be made private instead: `my_package/_module.py`. See other hiding techniques in the [Module layout](#module-layout) section.
351351

352-
GRIFFE: **Our recommendation — Expose public objects in single locations, use meaningful names.**
352+
GRIFFE: **Our recommendation — Expose public objects in single locations, use meaningful names.**
353353
We recommend making sure that each public object is exposed in a single location. Ensuring unique names might be more tricky depending on the code base, so we recommend ensuring meaningful names at least, not requiring the context of modules above to understand what the objects are for.
354354

355355
## Logger names
@@ -372,14 +372,14 @@ Could we emit a deprecation warning when users obtain the logger with the old na
372372

373373
Since it is essentially impossible to deprecate a logger name, we recommend to avoid using `__name__` as logger name, at the very least in private modules.
374374

375-
GRIFFE: **Our recommendation — Use a single logger.**
375+
GRIFFE: **Our recommendation — Use a single logger.**
376376
Absolutely avoid using `__name__` as logger name in private modules. If your module layout is hidden, or does not matter for logging purposes, just use the same logger everywhere by using your package name as logger name. Example: `logger = logging.getLogger("griffe")`. Show your users how to temporarily alter your global logger (typically with context managers) so that altering subloggers becomes unnecessary. Maybe even provide the utilities to do that.
377377

378378
## Documentation
379379

380380
Obviously, your public API should be documented. Each object should have a docstring that explains why the object is useful and how it is used. More on that in our [docstrings recommendations](docstrings.md). Docstrings work well for offline documentation; we recommend exposing your public API online too, for example with [MkDocs](https://www.mkdocs.org/) and [mkdocstrings' Python handler](https://mkdocstrings.github.io/python/), or with other SSGs (Static Site Generators). Prefer a tool that is able to create a [Sphinx-like](https://sphobjinv.readthedocs.io/en/stable/syntax.html) inventory of objects (an `objects.inv` file) that will allow other projects to easily cross-reference your API from their own documentation. Make sure each and every object of your public API is documented in your web docs and therefore added to the objects inventory (and maybe that nothing else is added to this inventory as "public API").
381381

382-
> GRIFFE: **Our recommendation — Document your public API extensively.**
382+
> GRIFFE: **Our recommendation — Document your public API extensively.**
383383
> Write docstrings for each and every object of your public API. Deploy online documentation where each object is documented and added to an object inventory that can be consumed by third-party projects. If you find yourself reluctant to document a public object, it means that this object should maybe be internal instead.
384384
>
385385
> Our documentation framework of choice is of course [MkDocs](https://www.mkdocs.org) combined with our [mkdocstrings](https://mkdocstrings.github.io/) plugin.
@@ -396,14 +396,14 @@ We invite you to check out our own test file: [`test_api.py`](https://github.com
396396
- all public objects are added to the inventory (which means they are documented in our API docs)
397397
- no private object is added to the inventory
398398

399-
GRIFFE: **Our recommendation — Test your API declaration early.**
399+
GRIFFE: **Our recommendation — Test your API declaration early.**
400400
The sooner you test your API declaration, the better your code base will evolve. This will force you to really think about how your API is exposed to your users. This will prevent mistakes like leaving a new object as public while you don't want users to start relying on it, or forgetting to expose a public object in your top-level module or to document it in your API docs.
401401

402402
## Linters
403403

404404
Depending on their configuration, many popular Python linters will warn you that you access or import private objects. This doesn't play well with hidden module layouts, where modules are private or moved under a private (sub-)package. Sometimes it doesn't even play well with private methods
405405

406-
> GRIFFE: **Our recommendation — Ignore "protected access" warnings for your own package, or make the warnings smarter.**
406+
> GRIFFE: **Our recommendation — Ignore "protected access" warnings for your own package, or make the warnings smarter.**
407407
> To users of linters, we recommend adding `# noqa` comments on the relevant code lines, or globally disabling warnings related to "private object access" if per-line exclusion requires too much maintenance.
408408
>
409409
> To authors of linters, we recommend (if possible) making these warnings smarter: they shouldn't be triggered when private objects are accessed from within the *same package*. Marking objects as private is meant to prevent downstream code from using them, not to prevent the developers of the current package themselves from using them: they know what they are doing and should be allowed to use their own private objects without warnings. At the same time, they don't want to disable these warnings *globally*, so the warnings should be derived in multiple versions, or made smarter.
@@ -497,7 +497,7 @@ Now instead of having to call `main(["dump", "..."])` in your tests, you can dir
497497

498498
The third and next users of your CLI as API are your users: just as you made your own life easier, you made their life easier for when they want to call some subcommands of your tool programmatically. No more messing with lists of strings without autocompletion or linting, no more patching of `sys.argv`, no more following the maze of transformations applied by this fancy CLI framework before finally reaching the crux of the subcommand you want to call, no more trying to replicate these transformations yourself with the CLI framework's API to avoid copy-pasting the dozens of lines you're only interested in.
499499

500-
> GRIFFE: **Our recommendation — Decouple command-line parsing from your CLI entrypoints.**
500+
> GRIFFE: **Our recommendation — Decouple command-line parsing from your CLI entrypoints.**
501501
> Do not tie the command parsing logic with your program's logic. Create functions early, make them accept arguments using basic types (`int`, `str`, `list`, etc.) so that your users can call your main command or subcommands with a single import and single statement. Do not encode all the logic in a single big `main` function. Decoupling the CLI-parsing logic from your entrypoints will make them much easier to test and use programmatically. Consider your entrypoints part of your API!
502502
>
503503
> Our CLI framework of choice is [Cappa](https://pypi.org/project/cappa/).
@@ -510,7 +510,7 @@ There are many different ways of deprecating previous usage of code, which depen
510510

511511
In addition to emitting deprecation warnings, you should also update the docstrings and documentation for the old usage to point at the new usage, add "deprecated" labels where possible, and mark objects as deprecated when possible.
512512

513-
GRIFFE: **Our recommendation — Allow a deprecation period, document deprecations.**
513+
GRIFFE: **Our recommendation — Allow a deprecation period, document deprecations.**
514514
Try allowing deprecation periods for every breaking change. Most changes can be made backward-compatible at the cost of writing legacy code. Use tools like [Yore](https://pawamoy.github.io/yore) to manage legacy code, and standard utilities like [`warnings.deprecated`][] to mark objects as deprecated. Griffe extensions such as [griffe-warnings-deprecated](https://mkdocstrings.github.io/griffe-warnings-deprecated/) can help you by dynamically augmenting docstrings for your API documentation.
515515

516516
## Third-party libraries
@@ -522,5 +522,3 @@ A few third-party libraries directly or indirectly related to public APIs deserv
522522
[modul](https://pypi.org/project/modul/), from Frost Ming, the author of [PDM](https://pdm-project.org/en/latest/), goes one step further and actually hides attributes that are not marked "exported" from users: they won't be able to access un-exported attributes, leaving *only* the public API visible.
523523

524524
[Deprecated](https://pypi.org/project/Deprecated/), which was probably a source of inspiration for [PEP 702](https://peps.python.org/pep-0702/), allows decorating objects with `@deprecated` to mark them as deprecated. Such decorated callables will emit deprecation warnings when called. PEP 702's `warnings.deprecated` could be seen as its successor, bringing the feature directly into the standard library so that type checkers and other static analysis tools can converge on this way to mark objects as deprecated.
525-
526-
[slothy](https://pypi.org/project/slothy/), which is less directly related to public APIs, but useful for the case where you are hiding your modules layout and exposing all your public API from the top-level `__init__` module. Depending on the size of your public API, and the time it takes to import everything (memory initializations, etc.), it might be interesting to make all these imports *lazy*. With a lazily imported public API, users who are only interested in a few objects of your public API won't have to pay the price of importing everything.

0 commit comments

Comments
 (0)