koji_habitude.models

koji-habitude - models

Core koji object models package.

author:

Christopher O’Brien <obriencj@gmail.com>

license:

GNU General Public License v3

ai-assistant:

Claude 3.5 Sonnet via Cursor

class koji_habitude.models.Add(obj: CoreObject)

Bases: Change

Adds a feature or relation to an existing object.

Subclasses should implement summary() to provide the addition action with verb (e.g., “Add package ‘httpd’”, “Add inheritance from ‘parent’”). The explain() method will automatically add “to {object}” context.

For complex cases, subclasses can override explain() directly.

Example summary(): “Add package ‘httpd’” Example explain(): “Add package ‘httpd’ to tag ‘f39-build’”

explain() → str

Return a full explanation of what this change will do.

By default, this adds “to {object}” context to the summary(). Override this method directly for complex cases where the two-stage approach doesn’t fit.

style_name: ClassVar[str] = 'add'
summary() → str

Return a minimal action summary with “Add” verb.

Override this method to provide a summary, or override explain() directly for complex formatting needs.

class koji_habitude.models.ArchiveType(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str = '', extensions: ~typing.List[str] = [], compression: ~typing.Literal['tar', 'zip', None] = None)

Bases: ArchiveTypeModel, CoreObject

Local archive type object from YAML.

change_report(resolver: Resolver) → ArchiveTypeChangeReport
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall
class koji_habitude.models.BaseModel

Bases: BaseModel

model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

koji_habitude.models.BaseObject

alias of CoreObject

class koji_habitude.models.BaseStatus(value)

Bases: Enum

Indicates the status of an object

DISCOVERED = 'discovered'

Dependency which is not defined in a Namespace, but is known to exist on the Koji instance

PENDING = 'pending'

Defined in a Namespace, but not known to exist on the Koji instance

PHANTOM = 'phantom'

Dependency which is not defined in a Namespace, and not known to exist on the Koji instance

PRESENT = 'present'

Defined in a Namespace, and known to exist on the Koji instance

class koji_habitude.models.BuildType(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None)

Bases: BuildTypeModel, CoreObject

Local build type object from YAML.

change_report(resolver: Resolver) → BuildTypeChangeReport
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteBuildType]
class koji_habitude.models.Change(obj: CoreObject)

Bases: object

Represents an atomic change in Koji, applicable to a single Base object.

apply(session: MultiCallSession) → None

Apply a change to the Koji instance. This will call the impl_apply method to perform the actual work, which will need to be overridden by subclasses.

Records the result of the change call, which can be accessed via the result method.

Parameters:

session – The Koji multicall session

Raises:

ChangeError – If the change has already been applied

break_multicall(resolver: Resolver) → bool

A special edge-case method to allows a change to break out of a multicall and be given its own fresh multicall. This is only used by the TagAddInheritance change, which needs to lookup and parent tag IDs in order to make its call. Those parents may not exist yet, if they are being added in the same multicall, and so that single change is permitted to return True from this method in order to let its parents first be created.

If the Koji API ever allows for the setInheritanceData call to operate on parent tags by name rather than by ID, then this method can be removed and the process for applying changes can be greatly simplified.

Parameters:

resolver – The resolver instance

Returns:

True if this change should break out of the current multicall

explain() → str

Return a human-readable explanation of what this change will do.

Subclasses should override this method to provide specific explanations.

Returns:

Human-readable explanation of the change

impl_apply(session: MultiCallSession) → VirtualCall

This method is called by the apply method to perform the actual work, and should not be called directly.

Subclasses of Change must implement this method to perform the actual work of applying the change to the Koji instance.

Parameters:

session – The Koji multicall session

Returns:

The result of the change call, to be recorded by this instance via the invoking apply method

obj: CoreObject
result() → Any

The result of the change call, as returned by the Koji session. If the change has not been applied, this will raise a ChangeError. If the call failed, this will raise the underlying exception returned by the Koji instance.

Note that this method is what allows a Change to determine whether it has failed or not. It’s possible that going into this, the state will be APPLIED, but if the call fails, the state will be FAILED.

Returns:

The result of the change call

Raises:

ChangeError – If the change has not been applied

skip() → None

Mark the change as skipped. This will prevent the apply method from being called, and will cause the result method to return None.

Raises:

ChangeError – If the change is not in PENDING state

skip_check(resolver: Resolver) → bool

Returns True if the change is skippable, and needs skipping, False otherwise. This is used in situations where the change depends on a phantom object (ie. is a Reference, and does not exist on the Koji instance)

Parameters:

resolver – The resolver instance

Returns:

True if the change should be skipped, False otherwise

skip_check_impl(resolver: Resolver) → bool

This method is called by the skip_check method to perform the skip determination, and should not be called directly.

Parameters:

resolver – The resolver instance

Returns:

True if the change should be skipped, False otherwise

property state: ChangeState
class koji_habitude.models.ChangeReport(obj: Resolvable, resolver: Resolver)

Bases: object

Represents a collection of changes applicable to a single Base object. A ChangeReport begins empty, and proceeds through the following phases in order to collect changes:

  • a Processor instance will create the ChangeReport instance from the initial Base object

  • state is PENDING

  • read is called by the Processor with a multicall session, allowing the report to invoke calls against Koji.

  • state is LOADING

  • read in turn calls impl_read to perform the actual work, provided

    that it has not been called already.

  • impl_read must be implemented by subclasses to perform the actual

    necessary calls to fetch the current koji state.

  • impl_read may return None to indicate that no follow-up calls are

    needed, or it may return a callable. The callable will be invoked in a separate, later multicall session, allowing the object to first check whether it exists in the system at all before proceeding with additional checks that will be guaranteed to fail if the object is not found.

  • the Processor allows the multicall session to be close, which populates the results of all the calls made in the impl_read call. If there are followup calls, they will be invoked in a new multicall session at that point.

  • state is LOADED

  • compare is called by a Processor, allowing a subclass to compare the current koji state with the expected state, and identify changes.

  • state is COMPARING

  • compare in turn calls impl_compare to perform the actual work.

  • impl_compare must be implemented by subclasses to perform the actual necessary calls to compare the current koji state (as obtained during the impl_read call) with the expected state of the Base object.

  • impl_compare yields individual differences to be recorded as Change instances which are collected by the calling compare method.

  • state is COMPARED

  • the Processor opens a new multicall session, and invokes the apply method on the report

  • state is APPLYING

  • the report’s apply method in turn invokes the apply (and indirectly impl_apply) methods of the changes in the report.

  • state is APPLIED

  • the Processor closes the multicall session, which causes each of the atomic changes to be applied into the Koji instance.

  • the Processor calls the check_results method on the report

  • state is CHECKING

  • the report’s check_results method in turn invokes the check_results methods of the changes in the report. This gives a chance for any exceptions to be raised if the change calls failed.

  • state is CHECKED

apply(session: MultiCallSession, skip_phantoms: bool = False) → None

Applies the changes in the report to the Koji instance by calling the apply method on each change.

Requires an initial state of COMPARED, and will progress through the APPLYING and APPLIED states.

break_multicall() → bool

Calls the break_multicall method on each change in the report until one returns True. Returns True if any change’s break_multicall method returns True, else returns False.

check_results() → None

Checks the results of the changes in the report. This will raise an exception if any change failed.

Requires an initial state of APPLIED, and will progress through the CHECKING and CHECKED states.

compare() → None

Compares the read Koji state with the expected state of the Base object by calling the impl_compare method and collectings its results.

Requires an initial state of LOADED, and will progress through the COMPARING and COMPARED states.

impl_compare() → Iterable[Change]

Must be implemented by subclasses to perform the actual work of comparing the read Koji state with the expected state of the Base object, yielding Change instances as they are identified.

impl_read(session: MultiCallSession) → Callable[[MultiCallSession], None] | None
impl_read_defer(session: MultiCallSession) → None
iter(skip_phantoms: bool = False, call_skip: bool = True) → Iterable[Change]

Iterates over the changes in the report, yielding each change.

Parameters:

skip_phantoms – Whether to skip phantoms

Returns:

Iterable of changes

read(session: MultiCallSession) → Callable[[MultiCallSession], None] | None

Reads the Koji state and compares it with the expected state of the Base object by calling the impl_read method.

Requires an initial state of PENDING, and will progress through the LOADING and LOADED states.

class koji_habitude.models.Channel(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, hosts: ~typing.List[str] = <factory>, exact_hosts: bool = False)

Bases: ChannelModel, CoreObject

Local channel object from YAML.

change_report(resolver: Resolver) → ChannelChangeReport
exact_hosts: bool
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteChannel]
class koji_habitude.models.ContentGenerator(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, users: ~typing.List[str] = <factory>, exact_users: bool = False)

Bases: ContentGeneratorModel, CoreObject

Local content generator object from YAML.

change_report(resolver: Resolver) → ContentGeneratorChangeReport
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteContentGenerator]
class koji_habitude.models.CoreModel(*, name: str, type: str | None = None)

Bases: IdentifiableMixin, BaseModel

A base model shared by a core and remote object

dependency_keys() → Sequence[Tuple[str, str]]
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class koji_habitude.models.CoreObject(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None)

Bases: LocalMixin, ResolvableMixin, BaseModel

Core models that load from YAML and have full functionality through the Resolver, Processor, and Solver.

can_split() → bool
change_report(resolver: Resolver) → ChangeReport
dependency_keys() → Sequence[Tuple[str, str]]
description: str | None
is_split() → bool
key() → Tuple[str, str]

Return the key of this object as a tuple of (typename, name)

model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

split() → CoreT
typename: ClassVar[str] = 'object'
was_split() → bool
class koji_habitude.models.Create(obj: CoreObject)

Bases: Change

Creates a new object in Koji.

Subclasses should implement summary() to provide a description of what’s being created (without the “Create” verb). The explain() method will automatically prepend “Create” to the summary.

For complex cases, subclasses can override explain() directly.

Example summary(): “tag ‘f39-build’ with arches [x86_64, aarch64]” Example explain(): “Create tag ‘f39-build’ with arches [x86_64, aarch64]”

explain() → str

Return a full explanation of what this change will do.

By default, this prepends “Create” to the summary(). Override this method directly for complex cases where the two-stage approach doesn’t fit.

Returns:

Full explanation of the change

style_name: ClassVar[str] = 'create'
summary() → str

Return a minimal description of what’s being created (without “Create” verb).

Override this method to provide a summary, or override explain() directly for complex formatting needs.

Returns:

Minimal description without the “Create” verb

class koji_habitude.models.DataMixin(*, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>)

Bases: LocalMixin

A mixin that loads from a local representation, and also stores a copy of that original dict as the data attribute.

property data: Dict[str, Any] | None

Access the raw data that was used if this object was created via from_dict

classmethod from_dict(data: Dict[str, Any]) → DataT

Create an instance directly from a dictionary. Records the original data dict for later review via the data property.

model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(context: Any, /) → None

This function is meant to behave like a BaseModel method to initialise private attributes.

It takes context as an argument since that’s what pydantic-core passes when calling it.

Parameters:
  • self – The BaseModel instance.

  • context – The context.

class koji_habitude.models.ExternalRepo(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, url: str)

Bases: ExternalRepoModel, CoreObject

Local external repository object from YAML.

change_report(resolver: Resolver) → ExternalRepoChangeReport
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteExternalRepo]
classmethod validate_url(v)
class koji_habitude.models.Group(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, enabled: bool = True, members: ~typing.List[str] = <factory>, permissions: ~typing.List[str] = <factory>, exact_members: bool = False, exact_permissions: bool = False)

Bases: GroupModel, CoreObject

Local group object from YAML.

change_report(resolver: Resolver) → GroupChangeReport
exact_members: bool
exact_permissions: bool
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteGroup]
class koji_habitude.models.Host(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, arches: ~typing.List[str] = <factory>, capacity: float | None = None, enabled: bool = True, channels: ~typing.List[str] = <factory>, exact_channels: bool = False)

Bases: HostModel, CoreObject

Local host object from YAML.

change_report(resolver: Resolver) → HostChangeReport
exact_channels: bool
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteHost]
split() → Host
class koji_habitude.models.IdentifiableMixin(*, name: str, type: str | None = None)

Bases: BaseModel

Pydantic mixin for Identifiable protocol

key() → Tuple[str, str]
model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

name: str
typename: ClassVar[str] = 'object'
classmethod validate_name(value: str)
yaml_type: str | None
class koji_habitude.models.LocalMixin(*, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>)

Bases: BaseModel

Pydantic mixin for Local protocol

filename: str | None
filepos() → Tuple[str | None, int | None]
filepos_str() → str
classmethod from_dict(data: Dict[str, Any]) → LocalT

Create an instance directly from a dictionary. Records the original data dict for later review via the data property.

lineno: int | None
model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

to_dict(**kwargs: Any) → Dict[str, Any]

Return a dictionary representation of this object. This is distinct from the original data that was used to create the object, and may include fields with default values and validated forms.

trace: List[Dict[str, Any]] | None
class koji_habitude.models.Modify(obj: CoreObject)

Bases: Change

Modifies an existing feature or relation of an object.

Subclasses should implement summary() to provide the modification action with verb (e.g., “Set package ‘httpd’ owner to ‘webteam’”, “Update group ‘build’ description”). The explain() method will automatically add “in {object}” context.

For complex cases, subclasses can override explain() directly.

Example summary(): “Set package ‘httpd’ owner to ‘webteam’” Example explain(): “Set package ‘httpd’ owner to ‘webteam’ in tag ‘f39-build’”

explain() → str

Return a full explanation of what this change will do.

By default, this adds “in {object}” context to the summary(). Override this method directly for complex cases where the two-stage approach doesn’t fit.

style_name: ClassVar[str] = 'modify'
summary() → str

Return a minimal action summary with verb.

Override this method to provide a summary, or override explain() directly for complex formatting needs.

class koji_habitude.models.Permission(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None)

Bases: PermissionModel, CoreObject

Local permission object from YAML.

change_report(resolver: Resolver) → PermissionChangeReport
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemotePermission]
class koji_habitude.models.Remove(obj: CoreObject)

Bases: Change

Removes a feature or relation from an existing object.

Subclasses should implement summary() to provide the removal action with verb (e.g., “Remove package ‘httpd’”, “Remove group ‘build’”). The explain() method will automatically add “from {object}” context.

For complex cases, subclasses can override explain() directly.

Example summary(): “Remove package ‘httpd’” Example explain(): “Remove package ‘httpd’ from tag ‘f39-build’”

explain() → str

Return a full explanation of what this change will do.

By default, this adds “from {object}” context to the summary(). Override this method directly for complex cases where the two-stage approach doesn’t fit.

style_name: ClassVar[str] = 'remove'
summary() → str

Return a minimal action summary with “Remove” verb.

Override this method to provide a summary, or override explain() directly for complex formatting needs.

class koji_habitude.models.ResolvableMixin(*, name: str, type: str | None = None)

Bases: IdentifiableMixin

is_phantom() → bool
load_remote(session: MultiCallSession, reload: bool = False) → VirtualCall
model_config: ClassVar[ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall
remote()
property status: BaseStatus
class koji_habitude.models.SubModel

Bases: BaseModel

A base model for submodels that need to be validated by alias and name

model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class koji_habitude.models.Tag(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, lock: bool = False, permission: str | None = None, arches: ~typing.List[str] = <factory>, maven_support: bool = False, maven_include_all: bool = False, extras: ~typing.Dict[str, ~typing.Any] = <factory>, block_extras: ~typing.List[str] = <factory>, groups: ~typing.Dict[str, ~koji_habitude.models.tag.TagGroup] = <factory>, inheritance: ~typing.List[~koji_habitude.models.tag.InheritanceLink] = <factory>, external_repos: ~typing.List[~koji_habitude.models.tag.ExternalRepoLink] = <factory>, packages: ~typing.List[~koji_habitude.models.tag.PackageEntry] = <factory>, exact_extras: bool = False, exact_groups: bool = False, exact_packages: bool = False)

Bases: TagModel, CoreObject

Local tag object from YAML.

change_report(resolver: Resolver) → TagChangeReport
classmethod convert_external_repos_from_simplified(data: Any) → Any
classmethod convert_groups_from_simplified(data: Any) → Any
classmethod convert_inheritance_from_simplified(data: Any) → Any
classmethod convert_packages_from_simplified(data: Any) → Any

we allow the packages field to be specified in a simplified manner, as a list of strings or a list of dictionaries. If it’s a string, the value is considered to be the name of the package.

exact_extras: bool
exact_groups: bool
exact_packages: bool
classmethod merge_packages(data: Any) → Any
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_Tag__context: Any) → None

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteTag]
split() → Tag
class koji_habitude.models.Target(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, build_tag: str, dest_tag: str | None = None)

Bases: TargetModel, CoreObject

Local target object from YAML.

change_report(resolver: Resolver) → TargetChangeReport
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteTarget]
class koji_habitude.models.Update(obj: CoreObject)

Bases: Change

Updates properties of an existing object.

Subclasses should implement summary() to provide the update action with verb (e.g., “Lock tag”, “Set permission to ‘admin’”). The explain() method will automatically add the object context.

For complex cases, subclasses can override explain() directly.

Example summary(): “Lock” Example explain(): “Lock tag ‘f39-build’”

explain() → str

Return a full explanation of what this change will do.

By default, this adds object context to the summary(). Override this method directly for complex cases where the two-stage approach doesn’t fit.

Returns:

Full explanation of the change

style_name: ClassVar[str] = 'update'
summary() → str

Return a minimal action summary with verb.

Override this method to provide a summary, or override explain() directly for complex formatting needs.

class koji_habitude.models.User(*, name: str, type: str | None = None, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>, description: str | None = None, enabled: bool | None = None, groups: ~typing.List[str] = <factory>, permissions: ~typing.List[str] = <factory>, exact_groups: bool = False, exact_permissions: bool = False)

Bases: UserModel, CoreObject

Local user object from YAML.

change_report(resolver: Resolver) → UserChangeReport
exact_groups: bool
exact_permissions: bool
model_config: ClassVar[ConfigDict] = {'validate_by_alias': True, 'validate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

model_post_init(_IdentifiableMixin__context: Any)

Override this method to perform additional initialization after __init__ and model_construct. This is useful if you want to do some validation that requires the entire model to be initialized.

classmethod query_remote(session: MultiCallSession, key: Tuple[str, str]) → VirtualCall[RemoteUser]
split() → User