class PulpSearch(object): # Helper class representing a prepared Pulp search. # Usually just a filters dict, but may include type_ids # and fields for unit searches. filters = attr.ib(type=dict) type_ids = attr.ib(type=list, default=None) unit_fields = attr.ib(type=list, default=None)
class FieldMatchCriteria(Criteria): _field = attr.ib() _matcher = attr.ib(converter=coerce_to_matcher) def __str__(self): matcher = str(self._matcher) out = "%s%s" % (self._field, matcher) if " " in matcher: out = "(%s)" % out return out
def pulp_attrib(pulp_field=None, pulp_py_converter=None, py_pulp_converter=None, **kwargs): """Drop-in replacement for attr.ib with added features: - applies a validator based on type automatically - supports pulplib-specific metadata via extra keyword arguments """ metadata = kwargs.get("metadata") or {} if pulp_field: metadata[PULP2_FIELD] = pulp_field if pulp_py_converter: metadata[PULP2_PY_CONVERTER] = pulp_py_converter if py_pulp_converter: metadata[PY_PULP2_CONVERTER] = py_pulp_converter if "type" in kwargs: # As a convenience, you may define string types as type=str # on any python version, but what you'll actually get is # whatever's the primary string type (e.g. basestr on py2) if kwargs["type"] is str: kwargs["type"] = six.string_types[0] # If you haven't defined a validator, you get one automatically # for your requested type if "validator" not in kwargs: kwargs["validator"] = attr.validators.optional( attr.validators.instance_of(kwargs["type"])) kwargs["metadata"] = metadata return attr.ib(**kwargs)
class LessThanMatcher(Matcher): _value = attr.ib() def _map(self, fn): return attr.evolve(self, value=fn(self._value)) def __str__(self): return "<%s" % repr(self._value)
class OrCriteria(Criteria): _operands = attr.ib() def __str__(self): if not self._operands: return "<empty OR>" if len(self._operands) == 1: return str(self._operands[0]) return "(" + " OR ".join([str(o) for o in self._operands]) + ")"
class WithClient(object): # A mixin for objects holding a private reference to client. _client = attr.ib(default=None, init=False, repr=False, cmp=False, hash=False) def _set_client(self, client): self.__dict__["_client"] = client
class InMatcher(Matcher): _values = attr.ib() @_values.validator def _check_values(self, _, values): if isinstance(values, Iterable) and not isinstance(values, six.string_types): return raise ValueError("Must be an iterable: %s" % repr(values)) def _map(self, fn): return attr.evolve(self, values=[fn(x) for x in self._values])
class RegexMatcher(Matcher): _pattern = attr.ib() @_pattern.validator def _check_pattern(self, _, pattern): # It must be a string. # Need an explicit check here because re.compile also succeeds # on already-compiled regex objects. if not isinstance(pattern, six.string_types): raise TypeError("Regex matcher expected string, got: %s" % repr(pattern)) # Verify that the given value can really be compiled as a regex. re.compile(pattern)
def pulp_attrib(pulp_field=None, pulp_py_converter=None, py_pulp_converter=None, **kwargs): metadata = kwargs.get("metadata") or {} if pulp_field: metadata[PULP2_FIELD] = pulp_field if pulp_py_converter: metadata[PULP2_PY_CONVERTER] = pulp_py_converter if py_pulp_converter: metadata[PY_PULP2_CONVERTER] = py_pulp_converter kwargs["metadata"] = metadata return attr.ib(**kwargs)
class RegexMatcher(Matcher): _pattern = attr.ib() @_pattern.validator def _check_pattern(self, _, pattern): # It must be a string. # Need an explicit check here because re.compile also succeeds # on already-compiled regex objects. if not isinstance(pattern, six.string_types): raise TypeError("Regex matcher expected string, got: %s" % repr(pattern)) # Verify that the given value can really be compiled as a regex. re.compile(pattern) # Note: regex matcher does not implement _map since regex is defined only # in terms of strings, there are no meaningful conversions. def __str__(self): return "=~/%s/" % self._pattern
class Task(PulpObject): """Represents a Pulp task.""" _SCHEMA = load_schema("task") id = pulp_attrib(type=str, pulp_field="task_id") """ID of this task (str).""" completed = attr.ib(default=None, type=bool) """True if this task has completed, successfully or otherwise. May be `None` if the state of this task is unknown. """ succeeded = attr.ib(default=None, type=bool) """True if this task has completed successfully. May be `None` if the state of this task is unknown. """ error_summary = attr.ib(default=None, type=str) """A summary of the reason for this task's failure (if any). This is a short string, generally a single line, suitable for display to users. The string includes the ID of the failed task. """ error_details = attr.ib(default=None, type=str) """Detailed information for this task's failure (if any). This may be a multi-line string and may include technical information such as a Python backtrace generated by Pulp. ``error_details`` is a superset of the information available via ``error_summary``, so it is not necessary to display both. """ tags = pulp_attrib(default=attr.Factory(list), type=list, pulp_field="tags") """The tags for this task. Typically includes info on the task's associated action and repo, such as: .. code-block:: python ["pulp:repository:rhel-7-server-rpms__7Server_x86_64", "pulp:action:publish"] """ repo_id = attr.ib(type=str) """The ID of the repository associated with this task, otherwise None.""" units_data = pulp_attrib(default=attr.Factory(list), type=list, pulp_field="result.units_successful") """Info on the units which were processed as part of this task (e.g. associated or unassociated). This is a list. The list elements are the raw dicts as returned by Pulp. These should at least contain a 'type_id' and a 'unit_key'. """ @repo_id.default def _repo_id_default(self): prefix = "pulp:repository:" for tag in self.tags or []: if tag.startswith(prefix): return tag[len(prefix):] return None @succeeded.validator def _check_succeeded(self, _, value): if value and not self.completed: raise ValueError( "Cannot have task with completed=False, succeeded=True") @classmethod def _data_to_init_args(cls, data): out = super(Task, cls)._data_to_init_args(data) state = data["state"] out["completed"] = state in ("finished", "error", "canceled", "skipped") out["succeeded"] = state in ("finished", "skipped") if state == "canceled": out["error_summary"] = "Pulp task [%s] was canceled" % data[ "task_id"] out["error_details"] = out["error_summary"] elif state == "error": out["error_summary"] = cls._error_summary(data) out["error_details"] = cls._error_details(data) return out @classmethod def _error_summary(cls, data): prefix = "Pulp task [%s] failed" % data["task_id"] error = data.get("error") if not error: return "%s: <unknown error>" % prefix return "%s: %s: %s" % (prefix, error["code"], error["description"]) @classmethod def _error_details(cls, data): out = cls._error_summary(data) error = data.get("error") if not error: return out # Error looks like this: # # { # 'code': u'PLP0001', # 'data': { # 'message': 'a message' # }, # 'description': 'A general pulp exception occurred', # 'sub_errors': [] # } # # See: https://docs.pulpproject.org/en/2.9/dev-guide/conventions/exceptions.html#error-details # # data can contain anything, or nothing. # It's only a convention that it often contains a message. # # sub_errors is currently ignored because I've never seen a non-empty # sub_errors yet. error_data = error.get("data") or {} messages = [] # Message in a general exception if error_data.get("message"): messages.append(error_data["message"]) # Some exceptions stash additional strings under details.errors if (error_data.get("details") or {}).get("errors"): error_messages = error_data["details"]["errors"] if isinstance(error_messages, list): messages.extend(error_messages) # Pulp docs refer to this as deprecated, but actually it's still # used and no alternative is provided. if data.get("traceback"): messages.append(data["traceback"]) message = "\n".join(messages) if message: # message can have CRLF line endings in rare cases. message = message.replace("\r\n", "\n").strip() out = "%s:\n%s" % (out, _indent(message)) return out
class EqMatcher(Matcher): _value = attr.ib() def _map(self, fn): return attr.evolve(self, value=fn(self._value))
class RegexMatcher(Matcher): _pattern = attr.ib() @_pattern.validator def _check_pattern(self, _, pattern): re.compile(pattern)
class UnitTypeMatchCriteria(FieldMatchCriteria): # This specialization of FieldMatchCriteria is used to match on unit types # while also keeping info on the fields of interest to the user. _unit_fields = attr.ib()
class OrCriteria(Criteria): _operands = attr.ib()
class AndCriteria(Criteria): _operands = attr.ib()
class FieldMatchCriteria(Criteria): _field = attr.ib() _matcher = attr.ib(converter=coerce_to_matcher)