#9443 Context manager for ipalib.api to automatically configure, connect, and disconnect
Closed: fixed by rcritten. Opened by cheimes.

Request for enhancement

As developer , I want a simple Python context manager to initialize and connect ipalib.api to an IPA server so that I don't have to learn implementation details of IPA's API plugin framework.

For now, users must call several ipalib.api methods in the right order (bootstrap, finalize, connect on the right backend, disconnect on the right backend) before they can execute a command. Some methods (bootstrap, finalize) must be called exactly once. api.Backend has different backend object depending on api.env.in_server property.

API should be as simple as:

from ipalib import api
if not api.isdone("bootstrap"):
    api.bootstrap(custom=settings)
with api:
    api.Commands.ping()

Proposal

Add context manager protocol to api.

The __enter__ method should
- support automatic TGT acquisition, see #9442
- finalize the api if it hasn't been finalized before: if not api.isdone("finalize"): api.finalize()
- allow users to call bootstrap or finalize before api.__enter__, so users can customize the API- automatically figure out if the api usesapi.Backend.ldap2orapi.Backend.rcpclientbased onapi.env.in_server` setting
- automatically connect the backend if it is not connected
- if the backend is connected, we may want to raise an exception to prevent nesting enter/exit.
- return self

The __exit__ method should
- disconnect the backend if it is still connected (it may have been disconnected already due to an internal error)


Metadata Update from @cheimes:
- Custom field rhbz adjusted to https://bugzilla.redhat.com/show_bug.cgi?id=1513934

Here is a working implementation for ipalib.API class.

    def __enter__(self):
        # Several IPA module require api.env at import time, some even
        # a fully finalized ipalib.ap, e.g. register() with MethodOverride.
        if self is not ipalib.api and not ipalib.api.isdone("finalize"):
            raise RuntimeError("global ipalib.api must be finalized first.")
        # initialize this api (might be global)
        if not self.isdone("finalize"):
            self.finalize()
        # connect backend, server and client use different backends.
        if self.env.in_server:
            conn = self.Backend.ldap2
        else:
            conn = self.Backend.rpcclient
        if conn.isconnected():
            raise RuntimeError("API is already connected")
        else:
            conn.connect()
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.env.in_server:
            conn = self.Backend.ldap2
        else:
            conn = self.Backend.rpcclient
        if conn.isconnected():
            conn.disconnect()

This looks nice, thank you. Please submit a PR with a simple test added, if that is not too much.

One note I would leave definitely in the documentation is that this implementation of context manager assumes connection and disconnection will happen around each use of the context manager. It might be something to consider performance-wise for a specific use case.

Metadata Update from @cheimes:
- Custom field on_review adjusted to https://github.com/freeipa/freeipa/pull/7003

master:

  • 6aebfe74fbcb970d5052ac7eedd310bd3cf5a277 Add context manager to ipalib.API

ipa-4-11:

  • ed094e11ec59409c6cb361fa871e9b5e3da02172 Add context manager to ipalib.API

Metadata Update from @rcritten:
- Issue close_status updated to: fixed
- Issue status updated to: Closed (was: Open)

Metadata