Skip to content

API reference

Generated from the library's docstrings. Everything here is imported from odouche, and nothing else is public: see Stability.

Client

odouche.Client

Client(
    session: Secret | None = None,
    *,
    read_only: bool = False,
)

Asks Odoo.sh on behalf of one session.

With no argument the session is the one in the environment, then the one stored by login. A tool that keeps sessions itself passes its own, which is never stored. Raises NoSessionError when there is none, SessionExpiredError when the stored one has passed its max age and KeyringUnavailableError when the keyring cannot be read.

Every call raises SessionExpiredError when Odoo.sh rejects the session, after deleting it if it is the stored one. Nothing is cached: each call asks Odoo.sh. Use the client as a context manager, or call close.

With read_only, a call that changes state on Odoo.sh raises ReadOnlyError and sends nothing.

session property

session: SessionInfo

Where the session came from and when it passes its max age. Odoo.sh is not asked.

read_only property

read_only: bool

Whether the client refuses the calls that change state on Odoo.sh.

close

close() -> None

Close the connections.

identity

identity() -> Identity

Return the user the session belongs to, which also tells that Odoo.sh still accepts it.

projects

projects() -> list[Project]

List the projects the session's user can reach.

branches

branches(project: Project | str) -> list[Branch]

List the branches of a project, given as a Project or by its name.

They come in the order Odoo.sh answers them, which is not by name or by stage. Raises NotFoundError when the project is not among those the session's user can reach, whether or not it exists, and PermissionDeniedError when Odoo.sh lists it but refuses its branches. Asks Odoo.sh twice, for the projects and then for the branches.

builds

builds(
    branch: Branch | int, *, limit: int = DEFAULT_LIMIT
) -> list[Build]

List the latest builds of a branch, given as a Branch or by its number, newest first.

At most limit builds are returned, in one request. Odoo.sh may answer fewer than asked for: it has only been seen to answer up to four, and older builds are out of reach. Raises NotFoundError when the branch is not one the session's user can reach.

build

build(branch: Branch | int, build_id: int) -> Build

Return one build of a branch by its number.

Odoo.sh has no request for a build by its number, so the build is looked for among the branch's latest. Raises NotFoundError when it is not there, which an older build is not.

latest_build

latest_build(branch: Branch | int) -> Build | None

Return the newest build of a branch, or None when it has none.

ssh_target

ssh_target(build: Build) -> SshTarget

Return the user and the host ssh reaches a build with. Odoo.sh is not asked.

Odoo.sh accepts there the keys registered on the user's account, not the session. Raises NotFoundError when the build has no host, and UpstreamChangedError when its number or its address is not one that can be handed to ssh as it is.

rebuild

rebuild(branch: Branch | int) -> Build

Change state on Odoo.sh: start a new build of a branch, given as a Branch or by its number.

Returns the new build, which replaces the branch's latest one. Only a development or a staging branch is rebuilt: any other raises StageRefusedError before the request.

The request is sent once. Raises OutcomeUnknownError when it left and Odoo.sh did not confirm it, or the new build cannot be found: look at the branch before calling again, since a second call can start a second build. Raises ReadOnlyError on a read-only client, and NotFoundError when the branch is not one the session's user can reach. A branch that is not a number raises TypeError.

check_rebuild

check_rebuild(branch: Branch) -> None

Raise StageRefusedError when rebuild would refuse the branch for its stage.

Odoo.sh is not asked and nothing changes, on a read-only client too.

watch_build

watch_build(
    project: Project | str,
    build: Build,
    *,
    timeout: float,
    pulse: float | None = None,
) -> Generator[Build]

Yield a build of a project as it is now, then at each change, and end once it has finished.

A change is one of status, result or status_info. The last build yielded is the finished one, and a build that has already finished is yielded once. The watch stays on this build: a newer one on the branch ends it as DROPPED. Closing the iterator closes the connection.

With pulse, the build is also yielded unchanged, every pulse seconds at least while nothing changes. An iterator is closed only between two builds, so a caller that may have to stop the watch asks for one.

Raises StreamTimeoutError after timeout seconds, UpstreamUnavailableError when the connection fails three times in a row or Odoo.sh cannot be asked for the build, and NotFoundError when the build is not among its branch's latest, or the project is not one the session's user can reach.

logs

logs(project: Project | str, build: Build) -> list[Log]

List the logs a build of a project has, which is none while it waits for a worker.

Raises NotFoundError when the build is not among its branch's latest, or the project is not one the session's user can reach.

read_log

read_log(
    project: Project | str,
    build: Build,
    kind: LogKind | str,
    *,
    tail: int | None = None,
) -> Generator[LogLine]

Yield the lines of one log of a build, given as a LogKind or by its name.

Log content is untrusted: it is whatever a process printed, terminal escape sequences and secrets of the instance included, and it is returned unchanged.

With tail, only the last lines are yielded, out of the log's last mebibyte. A line longer than 64 KiB is cut. Closing the iterator closes the connection. Raises NotFoundError when the build has no such log.

follow_log

follow_log(
    project: Project | str,
    build: Build,
    kind: LogKind | str,
    *,
    timeout: float,
    tail: int = 0,
    offset: int | None = None,
) -> Generator[LogLine]

Yield the lines of one log of a build as they are written, until the iterator is closed.

Log content is untrusted, as in read_log.

It starts after the log's last tail lines, or at offset, which is the offset of a line read before. Odoo.sh is asked every second. Raises StreamTimeoutError after timeout seconds, which math.inf makes no limit, UpstreamUnavailableError when a failed request is not answered after two more tries, and NotFoundError when the build has no such log.

odouche.Secret

Secret(value: str)

A sensitive string, such as an Odoo.sh session, that cannot print itself.

repr(), str() and format() return a placeholder, and pickling or copying raises TypeError. The value leaves only through expose_secret.

expose_secret

expose_secret() -> str

Return the wrapped value.

Logging in and out

odouche.login

login(
    *,
    ask: Callable[[], Secret] | None = None,
    notify: Callable[[LoginStep], None] = lambda _: None,
    timeout: float = 300,
) -> None

Log in to Odoo.sh and store the session in the keyring, replacing the stored one.

The user signs in with GitHub in a browser launched for the login, with a profile that is deleted when it ends. Where no browser can be launched, ask is called for the session_id cookie of www.odoo.sh: prompt for it without echo and return it wrapped in a Secret. notify is called with each LoginStep. The function itself neither prints nor prompts.

The session is stored once Odoo.sh has answered one request sent with it. Raises LoginTimeoutError when the sign-in has been waited for timeout seconds, LoginError when no session is obtained or Odoo.sh refuses the one pasted, and KeyringUnavailableError, before anything is asked of the user, when there is nowhere to store one or the keyring has not been unlocked within timeout seconds.

odouche.LoginStep

Bases: Enum

What a login is waiting for, for a frontend to say in its own words.

KEYRING class-attribute instance-attribute

KEYRING = 'keyring'

The keyring is being checked, and may be showing a dialog to be unlocked.

BROWSER class-attribute instance-attribute

BROWSER = 'browser'

A browser window is open on the Odoo.sh login, for the user to sign in with GitHub.

PASTE class-attribute instance-attribute

PASTE = 'paste'

No browser can be launched here: the session_id cookie is about to be asked for.

odouche.logout

logout(
    session: Secret | None = None,
    *,
    invalidate_given: bool = False,
) -> LogoutResult

Log out of Odoo.sh: end the stored session there, and delete it from the keyring.

The stored session is deleted even when Odoo.sh cannot be asked to end it. The result's failure then says why, and a copy of the session works until Odoo.sh expires it. With no session nothing is raised. A stored one past its max age, or that cannot be read, is deleted and not ended.

A session passed or set in the environment is not the library's to delete: the result names its source, and unsetting it is left to the caller. It is ended on Odoo.sh only with invalidate_given, since others may be using it. The stored session is then left as it is.

Raises NoSessionError when the session passed or set in the environment is not a cookie value, and KeyringUnavailableError when the keyring cannot be read or the session not deleted. When it is not deleted, the message says whether Odoo.sh ended it.

odouche.LogoutResult dataclass

LogoutResult(
    source: SessionSource | None,
    deleted: bool,
    invalidated: bool,
    failure: OdoucheError | None,
)

What a logout did.

source instance-attribute

source: SessionSource | None

Where the session came from, or None when there was none to log out of.

deleted instance-attribute

deleted: bool

Whether the stored session was deleted. One passed or set in the environment never is.

invalidated instance-attribute

invalidated: bool

Whether Odoo.sh has ended the session, so that a copy of it no longer works.

failure instance-attribute

failure: OdoucheError | None

Why Odoo.sh could not be asked to end the session, or None.

Session

odouche.Identity dataclass

Identity(
    user_id: int,
    name: str | None,
    username: str,
    email: str | None,
    session: SessionInfo,
)

The user a session belongs to, as Odoo.sh reports them.

user_id instance-attribute

user_id: int

The number Odoo.sh gives the user.

name instance-attribute

name: str | None

The user's display name, when Odoo.sh gives one.

username instance-attribute

username: str

The user's GitHub login.

email instance-attribute

email: str | None

The user's email address, when Odoo.sh gives one. It is personal data, carried as given.

session instance-attribute

session: SessionInfo

The session that was asked with.

odouche.SessionInfo dataclass

SessionInfo(
    source: SessionSource,
    stored_at: datetime | None,
    expires_at: datetime | None,
)

What is known of a session without asking Odoo.sh. It never holds the session.

source instance-attribute

source: SessionSource

Where the session came from.

stored_at instance-attribute

stored_at: datetime | None

When the login stored it, in UTC, or None when it is not the stored one.

expires_at instance-attribute

expires_at: datetime | None

When it passes the client-side max age, in UTC, or None when it is not the stored one.

odouche.SessionSource

Bases: StrEnum

Where a session came from.

ARGUMENT class-attribute instance-attribute

ARGUMENT = 'argument'

The caller passed it.

odouche.SESSION_ENV module-attribute

SESSION_ENV = 'ODOUCHE_SESSION'

The environment variable a session is read from. It wins over the keyring and is never stored.

odouche.KEYRING_SERVICE module-attribute

KEYRING_SERVICE = 'odouche'

The service the session is stored under in the OS keyring.

odouche.KEYRING_ENTRY module-attribute

KEYRING_ENTRY = 'session'

The name of the keyring entry, which holds the session and the time it was stored.

Projects, branches and builds

odouche.Project dataclass

Project(id: int, name: str, repository: str, url: str)

An Odoo.sh project the session's user can reach.

id instance-attribute

id: int

The number Odoo.sh gives the project.

name instance-attribute

name: str

The project's name on Odoo.sh, as in the address of its page.

repository instance-attribute

repository: str

The GitHub repository the project builds, as owner/name.

url instance-attribute

url: str

The address of the project's page on Odoo.sh.

odouche.Branch dataclass

Branch(id: int, name: str, stage: Stage, stage_name: str)

A branch of an Odoo.sh project.

id instance-attribute

id: int

The number Odoo.sh gives the branch.

name instance-attribute

name: str

The git branch.

stage instance-attribute

stage: Stage

The stage the branch sits in now.

stage_name instance-attribute

stage_name: str

What Odoo.sh calls that stage, such as dev.

odouche.Stage

Bases: StrEnum

The stage a branch sits in on Odoo.sh.

UNKNOWN class-attribute instance-attribute

UNKNOWN = 'unknown'

A stage the library does not know. The branch's stage_name holds what Odoo.sh calls it.

odouche.Build dataclass

Build(
    id: int,
    name: str,
    branch_id: int,
    branch_name: str,
    commit: Commit,
    status: BuildStatus,
    status_name: str,
    result: BuildResult | None,
    result_name: str | None,
    status_info: str | None,
    started_at: datetime | None,
    url: str | None,
)

What Odoo.sh made of a commit on a branch.

id instance-attribute

id: int

The number Odoo.sh gives the build.

name instance-attribute

name: str

The build's name on Odoo.sh, which is not derivable from its branch.

branch_id instance-attribute

branch_id: int

The number of the branch the build belongs to.

branch_name instance-attribute

branch_name: str

The git branch.

commit instance-attribute

commit: Commit

The commit the build was made from.

status instance-attribute

status: BuildStatus

Where the build is in its life.

status_name instance-attribute

status_name: str

What Odoo.sh calls that status, such as progress.

result instance-attribute

result: BuildResult | None

How the build ended, or None when Odoo.sh gives none. finished says whether it ended.

result_name instance-attribute

result_name: str | None

What Odoo.sh calls that result, such as success.

status_info instance-attribute

status_info: str | None

What the build is doing, in Odoo.sh's own words, such as Installing: account.

started_at instance-attribute

started_at: datetime | None

When a worker took the build, in UTC, or None while it waits for one.

url instance-attribute

url: str | None

The address of the build's database, when it has one.

finished property

finished: bool

Whether the build has ended, whatever its result.

DONE and DROPPED are the ended statuses Odoo.sh has been seen to give. SKIPPED and KILLED are counted as ended from their names alone. An unknown status is not finished.

odouche.BuildStatus

Bases: StrEnum

Where a build is in its life on Odoo.sh. How it ended is its BuildResult.

DROPPED class-attribute instance-attribute

DROPPED = 'dropped'

What a build becomes when a newer one replaces it.

UNKNOWN class-attribute instance-attribute

UNKNOWN = 'unknown'

A status the library does not know. The build's status_name holds what Odoo.sh calls it.

odouche.BuildResult

Bases: StrEnum

How a build ended.

UNKNOWN class-attribute instance-attribute

UNKNOWN = 'unknown'

A result the library does not know. The build's result_name holds what Odoo.sh calls it.

odouche.SshTarget dataclass

SshTarget(user: str, host: str)

Where ssh reaches a build: the user to log in as and the host.

user instance-attribute

user: str

The user on the build's host, which is the build's number.

host instance-attribute

host: str

The build's own host.

odouche.Commit dataclass

Commit(
    hash: str,
    message: str,
    author: str,
    timestamp: datetime,
    url: str,
)

The commit a build was made from, as Odoo.sh reports it.

hash instance-attribute

hash: str

The commit's full hash.

message instance-attribute

message: str

The whole commit message.

author instance-attribute

author: str

The author's name. It is another person's personal data, carried as Odoo.sh gives it.

timestamp instance-attribute

timestamp: datetime

When the commit was made, in UTC.

url instance-attribute

url: str

The address of the commit on GitHub.

Logs

odouche.Log dataclass

Log(
    kind: LogKind,
    name: str,
    modified_at: datetime,
    size: str,
)

One of the logs a build has.

kind instance-attribute

kind: LogKind

Which log it is.

name instance-attribute

name: str

What Odoo.sh calls it, such as install.

modified_at instance-attribute

modified_at: datetime

When it was last written to, in UTC.

size instance-attribute

size: str

Its size in Odoo.sh's own words, such as 156 KB.

odouche.LogKind

Bases: StrEnum

The logs Odoo.sh keeps of a build. A build has only some of them.

UNKNOWN class-attribute instance-attribute

UNKNOWN = 'unknown'

A log the library does not know. The log's name holds what Odoo.sh calls it.

odouche.LogLine dataclass

LogLine(text: str, offset: int, truncated: bool)

A line of a build's log. It is untrusted: whatever a process printed, unchanged.

text instance-attribute

text: str

The line without its newline. Bytes that are not UTF-8 are replaced.

offset instance-attribute

offset: int

The byte just past the line, which is where a follow can start again.

truncated instance-attribute

truncated: bool

Whether the line was longer than the library reads, and was cut.

Errors

odouche.OdoucheError

OdoucheError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: Exception

Base class of every error the library raises.

odouche.NoSessionError

NoSessionError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when there is no session: none was passed, set in the environment or stored.

One that is not a session_id cookie value is none.

The user has to log in.

odouche.SessionExpiredError

SessionExpiredError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when Odoo.sh rejects the session, or when it has passed the client-side max age.

The stored session is gone and the user has to log in again.

odouche.NotFoundError

NotFoundError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when the project, branch, build or log asked for is not one the session's user can reach.

odouche.PermissionDeniedError

PermissionDeniedError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when the session is valid but is not allowed to do what was asked.

odouche.UpstreamChangedError

UpstreamChangedError(
    operation: str, field: str, *, status: int | None = None
)

Bases: OdoucheError

Raised when an answer from Odoo.sh no longer has the shape the library reads.

Odoo.sh has no public API, so this is the expected way for the library to break. It is not a mistake in the caller's input.

odouche.UpstreamUnavailableError

UpstreamUnavailableError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when Odoo.sh cannot be reached, or answers with a server error.

odouche.ReadOnlyError

ReadOnlyError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when a read-only client is asked to change state on Odoo.sh. Nothing was sent.

odouche.StageRefusedError

StageRefusedError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when a branch is in a stage the library does not change. Nothing was changed.

odouche.OutcomeUnknownError

OutcomeUnknownError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when a state-changing request was sent and what Odoo.sh did with it is not known.

Look at Odoo.sh before trying again: the request is never repeated, since it may have been carried out.

odouche.StreamTimeoutError

StreamTimeoutError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when a stream is still open at its timeout. Nothing says Odoo.sh is unavailable.

odouche.KeyringUnavailableError

KeyringUnavailableError(
    message: str = _NO_KEYRING,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when no accepted keyring backend is available to store the session, or it stays locked.

Nothing was persisted, unless the message says a write did not finish. The message names the ways out.

odouche.LoginError

LoginError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when a login ends without a session: none could be captured, or Odoo.sh refused it.

Nothing was stored, and the session stored before the login is untouched.

odouche.LoginTimeoutError

LoginTimeoutError(
    message: str,
    *,
    operation: str | None = None,
    status: int | None = None,
)

Bases: OdoucheError

Raised when nobody completes the browser login before its timeout.

Nothing was stored, and the session stored before the login is untouched.

Version

odouche.__version__ module-attribute

__version__ = version('odouche')

The version of the installed odouche distribution.