AcademySoftwareFoundation / AcademySoftwareFoundation/rez

[Feature] Public API

Open
#634 0 comments 0 reactions 0 assignees View on GitHub
api enhancement
Dominant language
Python
Stars
1.1k
Forks
369
Avg merge
12d 3h
Merged PRs (30d)
5

Description

### Goal

Simplify and safeguard the developer's investment in Rez.

**Related**

- https://github.com/nerdvegas/rez/issues/65

### Motivation

Having gotten my feet wet with both the Rez CLI and API, I've gathered that what is referred to as an API actually is "All code", minus anything prefixed with `_` (and suffixed with `_` and `__`?).

That is surely flexible and convenient, but there are also a few issues with that.

1. **Huge API** It's true that ["everything in rez can be done in python"](https://github.com/nerdvegas/rez/wiki/Basic-Concepts#resolving-an-environment), but the same is true for any pure-Python library since it's dynamic and open source. It doesn't necessarily qualify as an "API".
2. **Poor documentation** And hard to produce; since there is nothing separating common functionality from not-so-common.
3. **Poor guarantees** Developers need to rely on an API to not change within a given major version. Internals should be able to change in backwards breaking ways, where the API acts as a converter to consistent input and output. The CLI currently does this (quite well, I think!)
4. **Rigid Codebase** Since internals are used as-is, it's difficult and impractical to guarantee that something will be around between releases, or that they will behave the same way. For example, functions and function signatures change over time to accommodate internal features and bug fixes, but then that would also affect the "API". Maintaining backwards compatibility for every single function/class/method is a tall order!

### Implementation

Add `rez/api.py` containing only public members, with guaranteed backwards compatibility and ease of discovery. Anything *not* in `api.py` should be considered internal and likely to change or disappear in backwards-incompatible ways.

```python
from rez import api

for package in api.iter_packages(["myPackage"]):
print(package.name)

context = api.ResolvedContext(["myPackage-1"])
```

**`api.py`**

```python
from .resolved_context import ResolvedContext
from .utils._version import __version__
from .packages_ import iter_packages
```

Followed by an auto-doc page from Sphinx, documenting just the members present in `api.py`.

**Examples**

I do this myself for any of my projects, and it's worked well consistently for many years.

- [Pyblish](https://github.com/pyblish/pyblish-base/blob/master/pyblish/api.py)
- [Avalon](https://github.com/getavalon/core/blob/master/avalon/api.py)

### Alternative

Some choose to expose public members via the `__init__.py`.

```python
import rez
context = rez.ResolvedContext(["myPackage-1"])
```

**`__init__.py`**

```python
from rez.resolved_context import ResolvedContext
from rez.utils._version import __version__
from rez.packages_ import iter_packages
...
```

Which although convenient and a little easier on the eye for the end-user, does have the problem that it creates a dependency between your API and every single module in the package. Which means that if e.g. `iter_packages` breaks, no module is able to operate. In an ideal world, this would be fine, and is surely a "fail fast" mentality, but during development when two or more things are under development, I've found this really difficult to manage as it forces you mentally context-switch between what you were doing to the whichever issue is thrown by this implicit and unintended dependency.

To solve the cosmetic benefit of `import rez`, and handle cases where there are more than a single "api" used in the same module, one could say `import rez.api as rez` along with e.g. `import pyblish.api as pyblish`.

Another advantage to an explicit `api.py` module is that it's clear which of your internal modules are meant as "high-level" and which are internal, as high-level ones - like the CLI - would then import the API and leverage the same simplicity as your users; along with "dog-fooding" your own codebase.

Contributor guide

Open the contributing guide

Research direction

Start by reviewing the proposed public surface in rez/api.py and the referenced ResolvedContext, __version__, and iter_packages imports, then read related issue #65. Define which members belong in the stable API and how the Sphinx auto-documentation should expose them. Done means a discoverable public API with the stated compatibility boundary and documentation for its members.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, developer-experience, documentation
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.