APIs publiques et privées

En Python, tout est public. Pour permettre aux développeurs de savoir sur quels composants ils peuvent compter, Flower déclare une API publique. Les composants qui font partie de l’API publique peuvent être utilisés. Les modifications apportées à l’API publique sont annoncées dans les notes de mise à jour et sont soumises à des politiques de dépréciation.

Tout ce qui ne fait pas partie de l’API publique fait partie de l’API privée. Même si Python permet d’y accéder, le code utilisateur ne doit jamais utiliser ces composants. Les API privées peuvent être modifiées à tout moment, même dans les versions de correctifs.

Comment déterminer si un composant fait partie de l’API publique ou non ? Facile :

Tout ce qui est listé dans la documentation de référence fait partie de l’API publique. Ce document explique comment les administrateurs de Flower définissent l’API publique et comment vous pouvez déterminer si un composant fait partie de l’API publique ou non en lisant le code source de Flower.

API publique de Flower

Flower dispose d’une API publique bien définie. Examinons-la plus en détail.

Important

Chaque composant accessible en suivant récursivement __init__.__all__ à partir du paquet racine (flwr) fait partie de l’API publique.

Si vous voulez déterminer si un composant (classe/fonction/générateur/…) fait partie de l’API publique ou non, vous devez commencer à la racine du paquet flwr. Utilisons tree -L 1 -d src/py/flwr pour regarder les sous-paquets Python contenus dans flwr :

flwr
├── cli
├── client
├── common
├── proto
├── server
└── simulation

Comparez cela avec la définition de all__'' dans la racine ``src/py/flwr/__init__.py :

# From `flwr/__init__.py`
__all__ = [
    "client",
    "common",
    "server",
    "simulation",
]

You can see that flwr has six subpackages (cli, client, common, proto, server, simulation), but only four of them are « exported » via __all__ (client, common, server, simulation).

What does this mean? It means that client, common, server and simulation are part of the public API, but cli and proto are not. The flwr subpackages cli and proto are private APIs. A private API can change completely from one release to the next (even in patch releases). It can change in a breaking way, it can be renamed (for example, flwr.cli could be renamed to flwr.command) and it can even be removed completely.

Therefore, as a Flower user:

  • from flwr import client ✅ Ok, you’re importing a public API.

  • from flwr import proto ❌ Not recommended, you’re importing a private API.

What about components that are nested deeper in the hierarchy? Let’s look at Flower strategies to see another typical pattern. Flower strategies like FedAvg are often imported using from flwr.server.strategy import FedAvg. Let’s look at src/py/flwr/server/strategy/__init__.py:

from .fedavg import FedAvg as FedAvg

# ... more imports

__all__ = [
    "FedAvg",
    # ... more exports
]

What’s notable here is that all strategies are implemented in dedicated modules (e.g., fedavg.py). In __init__.py, we import the components we want to make part of the public API and then export them via __all__. Note that we export the component itself (for example, the FedAvg class), but not the module it is defined in (for example, fedavg.py). This allows us to move the definition of FedAvg into a different module (or even a module in a subpackage) without breaking the public API (as long as we update the import path in __init__.py).

Therefore:

  • from flwr.server.strategy import FedAvg ✅ Ok, you’re importing a class that is part of the public API.

  • from flwr.server.strategy import fedavg ❌ Not recommended, you’re importing a private module.

This approach is also implemented in the tooling that automatically builds API reference docs.

Flower public API of private packages

We also use this to define the public API of private subpackages. Public, in this context, means the API that other flwr subpackages should use. For example, flwr.server.grid is a private subpackage (it’s not exported via src/py/flwr/server/__init__.py’s __all__).

Still, the private sub-package flwr.server.grid defines a « public » API using __all__ in src/py/flwr/server/grid/__init__.py:

from .grid import Driver, Grid
from .grpc_grid import GrpcGrid
from .inmemory_grid import InMemoryGrid

__all__ = [
    "Driver",
    "Grid",
    "GrpcGrid",
    "InMemoryGrid",
]

The interesting part is that both GrpcGrid and InMemoryGrid are never used by Flower framework users, only by other parts of the Flower framework codebase. Those other parts of the codebase import, for example, InMemoryGrid using from flwr.server.driver import InMemoryGrid (i.e., the InMemoryGrid exported via __all__), not from flwr.server.driver.in_memory_driver import InMemoryGrid (in_memory_driver.py is the module containing the actual InMemoryGrid class definition).

This is because flwr.server.driver defines a public interface for other flwr subpackages. This allows codeowners of flwr.server.driver to refactor the package without breaking other flwr-internal users.