-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor models to satisfy desired invariants #49
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,3 +47,4 @@ def fields_dict(cls): | |
fields = attr.fields | ||
evolve = attr.evolve | ||
has = attr.has | ||
validators = attr.validators |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
class FrozenList(list): | ||
# An immutable list subclass, intended for use on model fields. | ||
|
||
# Set of overridden methods is taken from collections.abc.MutableSequence. | ||
|
||
def __raise_immutable(self): | ||
raise NotImplementedError("cannot modify immutable list") | ||
|
||
def __setitem__(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def __delitem__(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def __iadd__(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def insert(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def append(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def extend(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def pop(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
def remove(self, *_args, **_kwargs): | ||
self.__raise_immutable() | ||
|
||
# FrozenList is hashable if everything within it is hashable | ||
def __hash__(self): | ||
return hash(tuple(self)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,13 @@ | ||
import datetime | ||
import logging | ||
|
||
from attr import validators | ||
from more_executors.futures import f_map | ||
|
||
from ..common import PulpObject, DetachedException | ||
from ..attr import pulp_attrib | ||
from ..distributor import Distributor | ||
from ..frozenlist import FrozenList | ||
from ...schema import load_schema | ||
from ... import compat_attr as attr | ||
|
||
|
@@ -31,22 +33,22 @@ class PublishOptions(object): | |
:meth:`~pubtools.pulplib.Repository.publish`. | ||
""" | ||
|
||
force = attr.ib(default=None, type=bool) | ||
force = pulp_attrib(default=None, type=bool) | ||
"""If True, Pulp should publish all data within a repository, rather than attempting | ||
to publish only changed data (or even skipping the publish). | ||
|
||
Setting ``force=True`` may have a major performance impact when publishing large repos. | ||
""" | ||
|
||
clean = attr.ib(default=None, type=bool) | ||
clean = pulp_attrib(default=None, type=bool) | ||
"""If True, certain publish tasks will not only publish new/changed content, but | ||
will also attempt to erase formerly published content which is no longer present | ||
in the repo. | ||
|
||
Setting ``clean=True`` generally implies ``force=True``. | ||
""" | ||
|
||
origin_only = attr.ib(default=None, type=bool) | ||
origin_only = pulp_attrib(default=None, type=bool) | ||
"""If ``True``, Pulp should only update the content units / origin path on | ||
remote hosts. | ||
|
||
|
@@ -89,14 +91,15 @@ class Repository(PulpObject): | |
""" | ||
|
||
distributors = pulp_attrib( | ||
default=attr.Factory(tuple), | ||
type=tuple, | ||
default=attr.Factory(FrozenList), | ||
type=list, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not "FrozenList"?
having list here makes it confusing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The attrs docs aren't quite accurate, or at least, aren't telling the whole story. These types do actually make it into the API as they are added as Python3 type annotations, where tools like pylint, mypy and sphinx look at them. I expect them to end up in our docs eventually, though it's not quite working right now (#24). For example, before this change the declared type of 'distributors' argument shows up in API as:
After this change, it will be 'list'. I do believe list is more appropriate than FrozenList here because:
|
||
pulp_field="distributors", | ||
pulp_py_converter=lambda ds: tuple([Distributor.from_data(d) for d in ds]), | ||
converter=FrozenList, | ||
pulp_py_converter=lambda ds: FrozenList([Distributor.from_data(d) for d in ds]), | ||
# It's too noisy to let repr descend into sub-objects | ||
repr=False, | ||
) | ||
"""tuple of :class:`~pubtools.pulplib.Distributor` objects belonging to this | ||
"""list of :class:`~pubtools.pulplib.Distributor` objects belonging to this | ||
repository. | ||
""" | ||
|
||
|
@@ -109,19 +112,24 @@ class Repository(PulpObject): | |
) | ||
"""ID of the product to which this repository belongs (if any).""" | ||
|
||
relative_url = attr.ib(default=None, type=str) | ||
relative_url = pulp_attrib(default=None, type=str) | ||
"""Default publish URL for this repository, relative to the Pulp content root.""" | ||
|
||
mutable_urls = attr.ib(default=attr.Factory(list), type=list, hash=False) | ||
mutable_urls = pulp_attrib( | ||
default=attr.Factory(FrozenList), type=list, converter=FrozenList | ||
) | ||
"""A list of URLs relative to repository publish root which are expected | ||
to change at every publish (if any content of repo changed).""" | ||
|
||
is_sigstore = attr.ib(default=False, type=bool) | ||
is_sigstore = pulp_attrib(default=False, type=bool) | ||
"""True if this is a sigstore repository, used for container image manifest | ||
signatures.""" | ||
|
||
is_temporary = pulp_attrib( | ||
default=False, type=bool, pulp_field="notes.pub_temp_repo" | ||
default=False, | ||
type=bool, | ||
validator=validators.instance_of(bool), | ||
pulp_field="notes.pub_temp_repo", | ||
) | ||
"""True if this is a temporary repository. | ||
|
||
|
@@ -134,22 +142,21 @@ class Repository(PulpObject): | |
""" | ||
|
||
signing_keys = pulp_attrib( | ||
default=attr.Factory(list), | ||
default=attr.Factory(FrozenList), | ||
type=list, | ||
pulp_field="notes.signatures", | ||
pulp_py_converter=lambda sigs: sigs.split(","), | ||
py_pulp_converter=",".join, | ||
converter=lambda keys: [k.strip() for k in keys], | ||
hash=False, | ||
converter=lambda keys: FrozenList([k.strip() for k in keys]), | ||
) | ||
"""A list of GPG signing key IDs used to sign content in this repository.""" | ||
|
||
skip_rsync_repodata = attr.ib(default=False, type=bool) | ||
skip_rsync_repodata = pulp_attrib(default=False, type=bool) | ||
"""True if this repository is explicitly configured such that a publish of | ||
this repository will not publish repository metadata to remote hosts. | ||
""" | ||
|
||
_client = attr.ib(default=None, init=False, repr=False, cmp=False) | ||
_client = attr.ib(default=None, init=False, repr=False, cmp=False, hash=False) | ||
# hidden attribute for client attached to this object | ||
|
||
@property | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What the benefit of having custom class instead of tuple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wrote explanation for that in 75790f1 commit message. In summary: the previous solution was to use tuple but it has usability issues because developers choose lists as the default container and tuples aren't as compatible with them as it may seem at first glance. For instance
(1, 2, 3) == [1, 2, 3]
isn't true.That means, if it continued using tuples, developers have to get in the habit of remembering where they need to write a tuple and where they need to write a list, or writing tuples everywhere (which is a tough ask), or running into weird bugs.