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:
ChangeAdds 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’”
- 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,CoreObjectLocal archive type object from YAML.
- 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.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:
EnumIndicates 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,CoreObjectLocal build type object from YAML.
- 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.Change(obj: CoreObject)
Bases:
objectRepresents 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:
objectRepresents 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.
- 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,CoreObjectLocal channel object from YAML.
- 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.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,CoreObjectLocal content generator object from YAML.
- 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.CoreModel(*, name: str, type: str | None = None)
Bases:
IdentifiableMixin,BaseModelA base model shared by a core and remote object
- 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,BaseModelCore models that load from YAML and have full functionality through the Resolver, Processor, and Solver.
- change_report(resolver: Resolver) ChangeReport
- 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
- class koji_habitude.models.Create(obj: CoreObject)
Bases:
ChangeCreates 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]”
- class koji_habitude.models.DataMixin(*, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>)
Bases:
LocalMixinA 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].
- 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,CoreObjectLocal external repository object from YAML.
- 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,CoreObjectLocal group object from YAML.
- 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.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,CoreObjectLocal host object from YAML.
- 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.IdentifiableMixin(*, name: str, type: str | None = None)
Bases:
BaseModelPydantic mixin for Identifiable protocol
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class koji_habitude.models.LocalMixin(*, __file__: str | None = None, __line__: int | None = None, __trace__: ~typing.List[~typing.Dict[str, ~typing.Any]] | None = <factory>)
Bases:
BaseModelPydantic mixin for Local protocol
- 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.
- model_config: ClassVar[ConfigDict] = {}
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
- class koji_habitude.models.Modify(obj: CoreObject)
Bases:
ChangeModifies 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’”
- 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,CoreObjectLocal permission object from YAML.
- 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.Remove(obj: CoreObject)
Bases:
ChangeRemoves 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’”
- class koji_habitude.models.ResolvableMixin(*, name: str, type: str | None = None)
Bases:
IdentifiableMixin- 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.
- remote()
- property status: BaseStatus
- class koji_habitude.models.SubModel
Bases:
BaseModelA 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,CoreObjectLocal tag object from YAML.
- 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.
- 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.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,CoreObjectLocal target object from YAML.
- 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.Update(obj: CoreObject)
Bases:
ChangeUpdates 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’”
- 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,CoreObjectLocal user object from YAML.
- 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].