#63 Refactor as a wrapper with the old API around libmodulemd/GI
Closed 6 years ago by nphilipp. Opened 6 years ago by nphilipp.
nphilipp/modulemd master--libmodulemd-wrapper  into  master

file modified
+16 -4
@@ -1,9 +1,21 @@ 

  The MIT License (MIT)

  

- Copyright (c) 2016  Red Hat, Inc.

+ Copyright © 2016 - 2018 Red Hat, Inc.

  

- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

+ Permission is hereby granted, free of charge, to any person obtaining a copy of

+ this software and associated documentation files (the "Software"), to deal in

+ the Software without restriction, including without limitation the rights to

+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies

+ of the Software, and to permit persons to whom the Software is furnished to do

+ so, subject to the following conditions:

  

- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

+ The above copyright notice and this permission notice shall be included in all

+ copies or substantial portions of the Software.

  

- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

+ SOFTWARE.

file modified
+1 -1
@@ -1,4 +1,4 @@ 

  recursive-include docs *

  include README.rst

- include spec.yaml

  include LICENSE

+ include modulemd/tests/test.yaml

file modified
+13 -16
@@ -1,25 +1,22 @@ 

- Module metadata definitions and the modulemd library

- ====================================================

+ Python wrapper around libmodulemd

+ =================================

  

- This repository contains simple module metadata template and the corresponding

- library for the manipulation thereof.

- 

- `spec.yaml <https://pagure.io/modulemd/blob/master/f/spec.yaml>`_:

-         This file serves two roles -- it is the input for tools generating the

-         actual module (such as pungi-modularization) and it is also present in

-         the resulting repository, available to its consumers (such as

-         fm-metadata-service).  For practical reasons, it is written in YAML.

-         See comments in the template for details.

+ This repository contains a Python wrapper around

+ `libmodulemd <https://github.com/fedora-modularity/libmodulemd>`_:

  

  modulemd:

-         A python library for manipulation of the proposed module metadata format.

-         API documentation is available at http://modulemd.readthedocs.org/.

+         A Python wrapper around the libmodulemd library for manipulation of the

+         module metadata format. API documentation is available at

+         http://modulemd.readthedocs.org/.

+ 

+ The ``spec.yaml`` file which was contained in older versions of this project is

+ obsolete. Refer to the up-to-date

+ `specification <https://github.com/fedora-modularity/libmodulemd/blob/master/spec.yaml>`_

+ in the ``libmodulemd`` project.

  

  Testing

  -------

  

  .. code-block:: bash

  

-    $ tox -e py27,py35

- 

- *See tox.ini for additional environments.*

+    $ python setup.py test

file modified
+273 -425
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -40,42 +39,30 @@ 

      mmd.dump("out.yaml")

  """

  

- from collections import OrderedDict

  import sys

  import datetime

- import dateutil.parser

  import yaml

  

- if sys.version_info > (3,):

-     long = int

- 

- from modulemd.api import ModuleAPI

- from modulemd.artifacts import ModuleArtifacts

- from modulemd.buildopts import ModuleBuildopts

- from modulemd.buildopts.rpms import ModuleBuildoptsRPMs

- from modulemd.components import ModuleComponents

- from modulemd.components.module import ModuleComponentModule

- from modulemd.components.rpm import ModuleComponentRPM

- from modulemd.filter import ModuleFilter

- from modulemd.profile import ModuleProfile

+ import gi

+ from gi.repository import GLib

+ gi.require_version('Modulemd', '0.2')

+ from gi.repository import Modulemd

  

- supported_mdversions = ( 1, )

+ from .api import ModuleAPI

+ from .artifacts import ModuleArtifacts

+ from .buildopts import ModuleBuildopts

+ from .components import ModuleComponents

+ from .filter import ModuleFilter

+ from .profile import ModuleProfile

  

- # From https://stackoverflow.com/a/16782282

- # Enable yaml handling of OrderedDict, rather than serialize

- # dict values alphabetically

- def _represent_ordereddict(dumper, data):

-     value = []

  

-     for item_key, item_value in data.items():

-         node_key = dumper.represent_data(item_key)

-         node_value = dumper.represent_data(item_value)

+ if sys.version_info > (3,):

+     integer_types = (int,)

+ else:

+     integer_types = (int, long)  # noqa

  

-         value.append((node_key, node_value))

+ supported_mdversions = (1,)

  

-     return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', value)

- yaml.representer.SafeRepresenter.add_representer(OrderedDict,

-                                                  _represent_ordereddict)

  

  def load_all(f):

      """Loads a metadata file containing multiple modulemd documents
@@ -83,9 +70,16 @@ 

  

      :param str f: File name to load

      """

-     with open(f, "r") as infile:

-         data = infile.read()

-     return loads_all(data)

+     l = list()

+     mmd_l = Modulemd.Module.new_all_from_file(f)

+ 

+     for mmd in mmd_l:

+         m = ModuleMetadata()

+         m.module = mmd

+         l.append(m)

+ 

+     return l

+ 

  

  def loads_all(s):

      """Loads multiple modulemd documents from a YAML multidocument
@@ -94,20 +88,28 @@ 

      :param str s: String containing multiple YAML documents.

      """

      l = list()

-     for doc in yaml.safe_load_all(s):

+     mmd_l = Modulemd.Module.new_all_from_string(s)

+ 

+     for mmd in mmd_l:

          m = ModuleMetadata()

-         m.loadd(doc)

+         m.module = mmd

          l.append(m)

+ 

      return l

  

+ 

  def dump_all(f, l):

      """Dumps a list of ModuleMetadata instances into a file.

  

      :param str f: Output filename

      :param list l: List of ModuleMetadata instances

      """

-     with open(f, "w") as outfile:

-         outfile.write(dumps_all(l))

+     mmds = list()

+     for module in l:

+         mmds.append(module.module)

+ 

+     return Modulemd.Module.dump_all(mmds, f)

+ 

  

  def dumps_all(l):

      """Dumps a list of ModuleMetadata instance into a YAML multidocument
@@ -115,8 +117,12 @@ 

  

      :param list l: List of ModuleMetadata instances

      """

-     return yaml.safe_dump_all([x._dumpd_ordered() for x in l],

-                               explicit_start=True)

+     mmds = list()

+     for module in l:

+         mmds.append(module.module)

+ 

+     return Modulemd.Module.dumps_all(mmds)

+ 

  

  class ModuleMetadata(object):

      """Class representing the whole module."""
@@ -125,6 +131,28 @@ 

  

      def __init__(self):

          """Creates a new ModuleMetadata instance."""

+         # Under the hood, we will use the ModulemdModule type

+         self.module = Modulemd.Module()

+ 

+         self.reset()

+ 

+     def __repr__(self):

+         return ("<ModuleMetadata: mdversion: {!r}, name: {!r}, stream: {!r},"

+                 " version: {!r}, context: {!r}, arch: {!r}, summary: {!r},"

+                 " description: {!r}, eol: {!r}, module_licenses: {!r},"

+                 " content_licenses: {!r}, buildrequires: {!r}, requires: {!r},"

+                 " community: {!r}, documentation: {!r}, tracker: {!r},"

+                 " xmd: {!r}, profiles: {!r}, api: {!r}, filter: {!r},"

+                 " components: {!r}, artifacts: {!r}>").format(

+                     self.mdversion, self.name, self.stream, self.version,

+                     self.context, self.arch, self.summary, self.description,

+                     self.eol, sorted(self.module_licenses),

+                     sorted(self.content_licenses), sorted(self.buildrequires),

+                     sorted(self.requires), self.community, self.documentation,

+                     self.tracker, sorted(self.xmd), sorted(self.profiles),

+                     self.api, self.filter, self.components, self.artifacts)

+ 

+     def reset(self):

          self.mdversion = max(supported_mdversions)

          self.name = ""

          self.stream = ""
@@ -134,85 +162,35 @@ 

          self.summary = ""

          self.description = ""

          self.eol = None

+         self.servicelevels = {}

          self.module_licenses = set()

          self.content_licenses = set()

-         self.buildrequires = dict()

-         self.requires = dict()

+         self.buildrequires = {}

+         self.requires = {}

          self.community = ""

          self.documentation = ""

          self.tracker = ""

-         self.xmd = dict()

-         self.profiles = dict()

-         self.api = ModuleAPI()

-         self.filter = ModuleFilter()

-         self.buildopts = ModuleBuildopts()

-         self.components = ModuleComponents()

-         self.artifacts = ModuleArtifacts()

- 

-     def __repr__(self):

-         return ("<ModuleMetadata: "

-                 "mdversion: {0}, "

-                 "name: {1}, "

-                 "stream: {2}, "

-                 "version: {3}, "

-                 "context: {4}, "

-                 "arch: {5}, "

-                 "summary: {6}, "

-                 "description: {7}, "

-                 "eol: {8}, "

-                 "module_licenses: {9}, "

-                 "content_licenses: {10}, "

-                 "buildrequires: {11}, "

-                 "requires: {12}, "

-                 "community: {13}, "

-                 "documentation: {14}, "

-                 "tracker: {15}, "

-                 "xmd: {16}, "

-                 "profiles: {17}, "

-                 "api: {18}, "

-                 "filter: {19}, "

-                 "components: {20}, "

-                 "artifacts: {21}>").format(

-                         repr(self.mdversion),

-                         repr(self.name),

-                         repr(self.stream),

-                         repr(self.version),

-                         repr(self.context),

-                         repr(self.arch),

-                         repr(self.summary),

-                         repr(self.description),

-                         repr(self.eol),

-                         repr(sorted(self.module_licenses)),

-                         repr(sorted(self.content_licenses)),

-                         repr(sorted(self.buildrequires)),

-                         repr(sorted(self.requires)),

-                         repr(self.community),

-                         repr(self.documentation),

-                         repr(self.tracker),

-                         repr(sorted(self.xmd)),

-                         repr(sorted(self.profiles)),

-                         repr(self.api),

-                         repr(self.filter),

-                         repr(self.components),

-                         repr(self.artifacts)

-                 )

+         self.xmd = {}

+         self.profiles = {}

+         self.api = ModuleAPI(parent=self)

+         self.filter = ModuleFilter(parent=self)

+         self.buildopts = ModuleBuildopts(parent=self)

+         self.components = ModuleComponents(parent=self)

+         self.artifacts = ModuleArtifacts(parent=self)

  

      def load(self, f):

          """Loads a metadata file into the instance.

  

          :param str f: File name to load

          """

-         with open(f, "r") as infile:

-             data = infile.read()

-         self.loads(data)

+         self.module = Modulemd.Module.new_from_file(f)

  

      def loads(self, s):

          """Loads metadata from a string.

  

          :param str s: Raw metadata in YAML

          """

-         yamld = yaml.safe_load(s)

-         self.loadd(yamld)

+         self.module = Modulemd.Module.new_from_string(s)

  

      def loadd(self, d):

          """Loads metadata from a dictionary.
@@ -220,285 +198,29 @@ 

          :param dict d: YAML metadata parsed into a dict

          :raises ValueError: If the metadata is invalid or unsupported.

          """

-         # header

-         if "document" not in d or d["document"] != "modulemd":

-             raise ValueError("The supplied data isn't a valid modulemd document")

-         if "version" not in d:

-             raise ValueError("Document version is required")

-         if d["version"] not in supported_mdversions:

-             raise ValueError("The supplied metadata version isn't supported")

-         self.mdversion = d["version"]

-         if "data" not in d or not isinstance(d["data"], dict):

-             raise ValueError("Data section missing or mangled")

-         # data

-         data = d["data"]

-         if "name" in data:

-             self.name = str(data["name"])

-         if "stream" in data:

-             self.stream = str(data["stream"])

-         if "version" in data:

-             self.version = int(data["version"])

-         if "context" in data:

-             self.context = str(data["context"])

-         if "arch" in data:

-             self.arch = str(data["arch"])

-         if "summary" in data:

-             self.summary = str(data["summary"])

-         if "description" in data:

-             self.description = str(data["description"])

-         if "eol" in data:

-             try:

-                 self.eol = dateutil.parser.parse(str(data["eol"])).date()

-             except:

-                 self.eol = None

-         if ("license" in data

-                 and isinstance(data["license"], dict)

-                 and "module" in data["license"]

-                 and data["license"]["module"]):

-             self.module_licenses = set(data["license"]["module"])

-         if ("license" in data

-                 and isinstance(data["license"], dict)

-                 and "content" in data["license"]):

-             self.content_licenses = set(data["license"]["content"])

-         if ("dependencies" in data

-                 and isinstance(data["dependencies"], dict)):

-             if ("buildrequires" in data["dependencies"]

-                     and isinstance(data["dependencies"]["buildrequires"], dict)):

-                 for n, s in data["dependencies"]["buildrequires"].items():

-                     self.add_buildrequires(str(n), str(s))

-             if ("requires" in data["dependencies"]

-                     and isinstance(data["dependencies"]["requires"], dict)):

-                 for n, s in data["dependencies"]["requires"].items():

-                     self.add_requires(str(n), str(s))

-         if "references" in data and data["references"]:

-             if "community" in data["references"]:

-                 self.community = data["references"]["community"]

-             if "documentation" in data["references"]:

-                 self.documentation = data["references"]["documentation"]

-             if "tracker" in data["references"]:

-                 self.tracker = data["references"]["tracker"]

-         if "xmd" in data:

-             self.xmd = data["xmd"]

-         if ("profiles" in data

-                 and isinstance(data["profiles"], dict)):

-             for profile in data["profiles"].keys():

-                 self.profiles[profile] = ModuleProfile()

-                 if "description" in data["profiles"][profile]:

-                     self.profiles[profile].description = \

-                         str(data["profiles"][profile]["description"])

-                 if "rpms" in data["profiles"][profile]:

-                     self.profiles[profile].rpms = \

-                         set(data["profiles"][profile]["rpms"])

-         if ("api" in data

-                 and isinstance(data["api"], dict)):

-             self.api = ModuleAPI()

-             if ("rpms" in data["api"]

-                     and isinstance(data["api"]["rpms"],list)):

-                 self.api.rpms = set(data["api"]["rpms"])

-         if ("filter" in data

-                 and isinstance(data["filter"], dict)):

-             self.filter = ModuleFilter()

-             if ("rpms" in data["filter"]

-                     and isinstance(data["filter"]["rpms"],list)):

-                 self.filter.rpms = set(data["filter"]["rpms"])

-         if ("buildopts" in data

-                 and isinstance(data["buildopts"], dict)):

-             self.buildopts = ModuleBuildopts()

-             if ("rpms" in data["buildopts"]

-                     and isinstance(data["buildopts"]["rpms"], dict)):

-                 self.buildopts.rpms = ModuleBuildoptsRPMs()

-                 if ("macros" in data["buildopts"]["rpms"]

-                         and isinstance(data["buildopts"]["rpms"]["macros"], str)):

-                     self.buildopts.rpms.macros = data["buildopts"]["rpms"]["macros"]

-         if ("components" in data

-                 and isinstance(data["components"], dict)):

-             self.components = ModuleComponents()

-             if "rpms" in data["components"]:

-                 for p, e in data["components"]["rpms"].items():

-                     extras = dict()

-                     extras["rationale"] = e["rationale"]

-                     if "buildorder" in e:

-                         extras["buildorder"] = int(e["buildorder"])

-                     if "repository" in e:

-                         extras["repository"] = str(e["repository"])

-                     if "cache" in e:

-                         extras["cache"] = str(e["cache"])

-                     if "ref" in e:

-                         extras["ref"] = str(e["ref"])

-                     if ("arches" in e

-                             and isinstance(e["arches"], list)):

-                         extras["arches"] = set(str(x) for x in e["arches"])

-                     if ("multilib" in e

-                             and isinstance(e["multilib"], list)):

-                         extras["multilib"] = set(str(x) for x in e["multilib"])

-                     self.components.add_rpm(p, **extras)

-             if "modules" in data["components"]:

-                 for p, e in data["components"]["modules"].items():

-                     extras = dict()

-                     extras["rationale"] = e["rationale"]

-                     if "buildorder" in e:

-                         extras["buildorder"] = int(e["buildorder"])

-                     if "repository" in e:

-                         extras["repository"] = str(e["repository"])

-                     if "ref" in e:

-                         extras["ref"] = str(e["ref"])

-                     self.components.add_module(p, **extras)

-         if ("artifacts" in data

-                 and isinstance(data["artifacts"], dict)):

-             self.artifacts = ModuleArtifacts()

-             if ("rpms" in data["artifacts"]

-                     and isinstance(data["artifacts"]["rpms"],list)):

-                 self.artifacts.rpms = set(data["artifacts"]["rpms"])

+         self.loads(yaml.dump(d))

+ 

  

      def dump(self, f):

          """Dumps the metadata into the supplied file.

  

          :param str f: File name of the destination

          """

-         data = self.dumps()

-         with open(f, "w") as outfile:

-             outfile.write(data)

- 

-     def _dumpd_ordered(self):

-         """Dumps the metadata into a OrderedDict.

- 

-         :rtype: collections.OrderedDict

-         """

-         doc = OrderedDict()

-         # header

-         doc["document"] = "modulemd"

-         doc["version"] = self.mdversion

-         # data

-         d = OrderedDict()

-         if self.name:

-             d["name"] = self.name

-         if self.stream:

-             d["stream"] = self.stream

-         if self.version:

-             d["version"] = self.version

-         if self.context:

-             d["context"] = self.context

-         if self.arch:

-             d["arch"] = self.arch

-         d["summary"] = self.summary

-         d["description"] = self.description

-         if self.eol:

-             d["eol"] = str(self.eol)

-         d["license"] = OrderedDict()

-         d["license"]["module"] = sorted(list(self.module_licenses))

-         if self.content_licenses:

-             d["license"]["content"] = sorted(list(self.content_licenses))

-         if self.buildrequires or self.requires:

-             d["dependencies"] = OrderedDict()

-             if self.buildrequires:

-                 d["dependencies"]["buildrequires"] = self.buildrequires

-             if self.requires:

-                 d["dependencies"]["requires"] = self.requires

-         if self.community or self.documentation or self.tracker:

-             d["references"] = OrderedDict()

-             if self.community:

-                 d["references"]["community"] = self.community

-             if self.documentation:

-                 d["references"]["documentation"] = self.documentation

-             if self.tracker:

-                 d["references"]["tracker"] = self.tracker

-         if self.xmd:

-             d["xmd"] = self.xmd

-         if self.profiles:

-             d["profiles"] = OrderedDict()

-             for profile in self.profiles.keys():

-                 if self.profiles[profile].description:

-                     if profile not in d["profiles"]:

-                         d["profiles"][profile] = OrderedDict()

-                     d["profiles"][profile]["description"] = \

-                         str(self.profiles[profile].description)

-                 if self.profiles[profile].rpms:

-                     if profile not in d["profiles"]:

-                         d["profiles"][profile] = OrderedDict()

-                     d["profiles"][profile]["rpms"] = \

-                         sorted(list(self.profiles[profile].rpms))

-         if self.api:

-             d["api"] = OrderedDict()

-             if self.api.rpms:

-                 d["api"]["rpms"] = sorted(list(self.api.rpms))

-         if self.filter:

-             d["filter"] = OrderedDict()

-             if self.filter.rpms:

-                 d["filter"]["rpms"] = sorted(list(self.filter.rpms))

-         if self.buildopts:

-             d["buildopts"] = OrderedDict()

-             if self.buildopts.rpms:

-                 d["buildopts"]["rpms"] = OrderedDict()

-                 if self.buildopts.rpms.macros:

-                     d["buildopts"]["rpms"]["macros"] = \

-                             self.buildopts.rpms.macros

-         if self.components:

-             d["components"] = OrderedDict()

-             if self.components.rpms:

-                 d["components"]["rpms"] = OrderedDict()

-                 for p in self.components.rpms.values():

-                     extra = OrderedDict()

-                     extra["rationale"] = p.rationale

-                     if p.buildorder:

-                         extra["buildorder"] = p.buildorder

-                     if p.repository:

-                         extra["repository"] = p.repository

-                     if p.ref:

-                         extra["ref"] = p.ref

-                     if p.cache:

-                         extra["cache"] = p.cache

-                     if p.arches:

-                         extra["arches"] = sorted(list(p.arches))

-                     if p.multilib:

-                         extra["multilib"] = sorted(list(p.multilib))

-                     d["components"]["rpms"][p.name] = extra

-             if self.components.modules:

-                 d["components"]["modules"] = OrderedDict()

-                 for p in self.components.modules.values():

-                     extra = OrderedDict()

-                     extra["rationale"] = p.rationale

-                     if p.buildorder:

-                         extra["buildorder"] = p.buildorder

-                     if p.repository:

-                         extra["repository"] = p.repository

-                     if p.ref:

-                         extra["ref"] = p.ref

-                     d["components"]["modules"][p.name] = extra

-         if self.artifacts:

-             d["artifacts"] = OrderedDict()

-             if self.artifacts.rpms:

-                 d["artifacts"]["rpms"] = sorted(list(self.artifacts.rpms))

-         doc["data"] = d

-         return doc

+         self.module.dump(f)

  

      def dumpd(self):

          """Dumps the metadata into a dictionary.

  

          :rtype: dict

          """

-         def _convert_ordered(orig, new):

-             """Recurse over a nested OrderedDict, converting each to

-             a dict()

-             """

-             for key, val in orig.items():

-                 if not isinstance(val, OrderedDict):

-                     new[key] = val

-                     continue

- 

-                 new[key] = dict()

-                 _convert_ordered(val, new[key])

- 

-         ordered = self._dumpd_ordered()

-         converted = dict()

-         _convert_ordered(ordered, converted)

-         return converted

+         return yaml.safe_load(self.dumps())

  

      def dumps(self):

          """Dumps the metadata into a string.

  

          :rtype: str

          """

-         return yaml.safe_dump(self._dumpd_ordered(), default_flow_style=False)

+         return self.module.dumps()

  

      @property

      def mdversion(self):
@@ -509,115 +231,139 @@ 

          changed to one of the supported_mdversions to alter the output

          format.

          """

-         return self._mdversion

+         return self.module.get_mdversion()

  

      @mdversion.setter

      def mdversion(self, i):

-         if not isinstance(i, (int, long)):

+         if not isinstance(i, integer_types):

              raise TypeError("mdversion: data type not supported")

          if i not in supported_mdversions:

              raise ValueError("mdversion: document version not supported")

-         self._mdversion = int(i)

+         self.module.set_mdversion(int(i))

  

      @property

      def name(self):

          """A string property representing the name of the module."""

-         return self._name

+         return self.module.get_name()

  

      @name.setter

      def name(self, s):

          if not isinstance(s, str):

              raise TypeError("name: data type not supported")

-         self._name = s

+         self.module.set_name(s)

  

      @property

      def stream(self):

          """A string property representing the stream name of the module."""

-         return self._stream

+         return self.module.get_stream()

  

      @stream.setter

      def stream(self, s):

          if not isinstance(s, str):

              raise TypeError("stream: data type not supported")

-         self._stream = str(s)

+         self.module.set_stream(s)

  

      @property

      def version(self):

          """An integer property representing the version of the module."""

-         return self._version

+         return self.module.get_version()

  

      @version.setter

      def version(self, i):

-         if not isinstance(i, (int, long)):

+         if not isinstance(i, integer_types):

              raise TypeError("version: data type not supported")

          if i < 0:

              raise ValueError("version: version cannot be negative")

-         self._version = i

+         self.module.set_version(i)

  

      @property

      def context(self):

          """A string property representing the context flag of the module."""

-         return self._context

+         return self.module.get_context()

  

      @context.setter

      def context(self, s):

          if not isinstance(s, str):

              raise TypeError("context: data type not supported")

-         self._context = s

+         self.module.set_context(s)

  

      @property

      def arch(self):

          """A string property representing the module artifacts' hardware

          architecture compatibility.

          """

-         return self._arch

+         return self.module.get_arch()

  

      @arch.setter

      def arch(self, s):

          if not isinstance(s, str):

              raise TypeError("arch: data type not supported")

-         self._arch = s

+         self.module.set_arch(s)

  

      @property

      def summary(self):

          """A string property representing a short summary of the module."""

-         return self._summary

+         return self.module.get_summary()

  

      @summary.setter

      def summary(self, s):

          if not isinstance(s, str):

              raise TypeError("summary: data type not supported")

-         self._summary = s

+         self.module.set_summary(s)

  

      @property

      def description(self):

          """A string property representing a detailed description of the

          module."""

-         return self._description

+         return self.module.get_description()

  

      @description.setter

      def description(self, s):

          if not isinstance(s, str):

              raise TypeError("description: data type not supported")

-         self._description = s

+         self.module.set_description(s)

  

      @property

      def eol(self):

          """A datetime.date property representing the module's EOL date.

          May be None if no EOL is defined."""

-         return self._eol

+ 

+         if not self.module.get_eol():

+             return None

+ 

+         eol = datetime.date(self.module.get_eol().get_year(),

+                             self.module.get_eol().get_month(),

+                             self.module.get_eol().get_day())

+         return eol

  

      @eol.setter

      def eol(self, o):

          if not isinstance(o, datetime.date) and o is not None:

              raise TypeError("eol: data type not supported")

-         self._eol = o

+ 

+         if o:

+             d = GLib.Date.new_dmy(o.day, o.month, o.year)

+         else:

+             d = None

+         self.module.set_eol(d)

+ 

+     @property

+     def servicelevels(self):

+         """A dictionary of service levels applying to this module"""

+         return self.module.get_servicelevels()

+ 

+     @servicelevels.setter

+     def servicelevels(self, d):

+         if not isinstance(d, dict):

+             raise TypeError("servicelevels: data type not supported")

+ 

+         self.module.set_servicelevels(d)

  

      @property

      def module_licenses(self):

          """A set of strings, a property, representing the license terms

          of the module itself."""

-         return self._module_licenses

+         return set(sorted(self.module.get_module_licenses().get()))

  

      @module_licenses.setter

      def module_licenses(self, ss):
@@ -626,7 +372,10 @@ 

          for s in ss:

              if not isinstance(s, str):

                  raise TypeError("module_licenses: data type not supported")

-         self._module_licenses = ss

+ 

+         simpleset = Modulemd.SimpleSet()

+         simpleset.set(list(ss))

+         self.module.set_module_licenses(simpleset)

  

      def add_module_license(self, s):

          """Adds a module license to the set.
@@ -635,7 +384,9 @@ 

          """

          if not isinstance(s, str):

              raise TypeError("add_module_license: data type not supported")

-         self._module_licenses.add(s)

+         simpleset = self.module.get_module_licenses()

+         simpleset.add(s)

+         self.module.set_module_licenses(simpleset)

  

      def del_module_license(self, s):

          """Removes the supplied license from the module licenses set.
@@ -644,17 +395,19 @@ 

          """

          if not isinstance(s, str):

              raise TypeError("del_module_license: data type not supported")

-         self._module_licenses.discard(s)

+         licenses = self.module.get_module_licenses()

+         licenses.remove(s)

+         self.module.set_module_licenses(licenses)

  

      def clear_module_licenses(self):

          """Clears the module licenses set."""

-         self._module_licenses.clear()

+         self.module.set_module_licenses(Modulemd.SimpleSet())

  

      @property

      def content_licenses(self):

          """A set of strings, a property, representing the license terms

          of the module contents."""

-         return self._content_licenses

+         return set(sorted(self.module.get_content_licenses().get()))

  

      @content_licenses.setter

      def content_licenses(self, ss):
@@ -663,7 +416,9 @@ 

          for s in ss:

              if not isinstance(s, str):

                  raise TypeError("content_licenses: data type not supported")

-         self._content_licenses = ss

+         simpleset = Modulemd.SimpleSet()

+         simpleset.set(list(ss))

+         self.module.set_content_licenses(simpleset)

  

      def add_content_license(self, s):

          """Adds a content license to the set.
@@ -672,7 +427,9 @@ 

          """

          if not isinstance(s, str):

              raise TypeError("add_content_license: data type not supported")

-         self._content_licenses.add(s)

+         simpleset = self.module.get_content_licenses()

+         simpleset.add(s)

+         self.module.set_content_licenses(simpleset)

  

      def del_content_license(self, s):

          """Removes the supplied license from the content licenses set.
@@ -681,11 +438,13 @@ 

          """

          if not isinstance(s, str):

              raise TypeError("del_content_license: data type not supported")

-         self._content_licenses.discard(s)

+         licenses = self.module.get_content_licenses()

+         licenses.remove(s)

+         self.module.set_content_licenses(licenses)

  

      def clear_content_licenses(self):

          """Clears the content licenses set."""

-         self._content_licenses.clear()

+         self.module.set_content_licenses(Modulemd.SimpleSet())

  

      @property

      def requires(self):
@@ -695,7 +454,7 @@ 

          Keys are the required module names (strings), values are their

          required stream names (also strings).

          """

-         return self._requires

+         return self.module.get_requires()

  

      @requires.setter

      def requires(self, d):
@@ -704,7 +463,7 @@ 

          for k, v in d.items():

              if not isinstance(k, str) or not isinstance(v, str):

                  raise TypeError("requires: data type not supported")

-         self._requires = d

+         self.module.set_requires(d)

  

      def add_requires(self, n, v):

          """Adds a required module dependency.
@@ -714,7 +473,9 @@ 

          """

          if not isinstance(n, str) or not isinstance(v, str):

              raise TypeError("add_requires: data type not supported")

-         self._requires[n] = v

+         d = self.module.get_requires()

+         d[n] = v

+         self.module.set_requires(d)

  

      update_requires = add_requires

  
@@ -725,12 +486,16 @@ 

          """

          if not isinstance(n, str):

              raise TypeError("del_requires: data type not supported")

-         if n in self._requires:

-             del self._requires[n]

+ 

+         d = self.module.get_requires()

+ 

+         if n in d:

+             del d[n]

+             self.module.set_requires(d)

  

      def clear_requires(self):

          """Removes all required runtime dependencies."""

-         self._requires.clear()

+         self.module.set_requires(dict())

  

      @property

      def buildrequires(self):
@@ -740,7 +505,7 @@ 

          Keys are the required module names (strings), values are their

          required stream names (also strings).

          """

-         return self._buildrequires

+         return self.module.get_buildrequires()

  

      @buildrequires.setter

      def buildrequires(self, d):
@@ -749,7 +514,7 @@ 

          for k, v in d.items():

              if not isinstance(k, str) or not isinstance(v, str):

                  raise TypeError("buildrequires: data type not supported")

-         self._buildrequires = d

+         self.module.set_buildrequires(d)

  

      def add_buildrequires(self, n, v):

          """Adds a module build dependency.
@@ -759,7 +524,10 @@ 

          """

          if not isinstance(n, str) or not isinstance(v, str):

              raise TypeError("add_buildrequires: data type not supported")

-         self._buildrequires[n] = v

+ 

+         d = self.module.get_buildrequires()

+         d[n] = v

+         self.module.set_buildrequires(d)

  

      update_buildrequires = add_buildrequires

  
@@ -770,70 +538,130 @@ 

          """

          if not isinstance(n, str):

              raise TypeError("del_buildrequires: data type not supported")

-         if n in self._buildrequires:

-             del self._buildrequires[n]

+ 

+         d = self.module.get_buildrequires()

+ 

+         if n in d:

+             del d[n]

+             self.module.set_buildrequires(d)

  

      def clear_buildrequires(self):

          """Removes all build dependencies."""

-         self._buildrequires.clear()

+         self.module.set_buildrequires(dict())

  

      @property

      def community(self):

          """A string property representing a link to the upstream community

          for this module.

          """

-         return self._community

+         return self.module.get_community()

  

      @community.setter

      def community(self, s):

          # TODO: Check if it looks like a link, unless empty

          if not isinstance(s, str):

              raise TypeError("community: data type not supported")

-         self._community = s

+         self.module.set_community(s)

  

      @property

      def documentation(self):

          """A string property representing a link to the upstream

          documentation for this module.

          """

-         return self._documentation

+         return self.module.get_documentation()

  

      @documentation.setter

      def documentation(self, s):

          # TODO: Check if it looks like a link, unless empty

          if not isinstance(s, str):

              raise TypeError("documentation: data type not supported")

-         self._documentation = s

+         self.module.set_documentation(s)

  

      @property

      def tracker(self):

          """A string property representing a link to the upstream bug tracker

          for this module.

          """

-         return self._tracker

+         return self.module.get_tracker()

  

      @tracker.setter

      def tracker(self, s):

          # TODO: Check if it looks like a link, unless empty

          if not isinstance(s, str):

              raise TypeError("tracker: data type not supported")

-         self._tracker = s

+         self.module.set_tracker(s)

  

      @property

      def xmd(self):

          """A dictionary property containing user-defined data."""

-         return self._xmd

+         d = dict()

+         for key, value in self.module.get_xmd().items():

+             d[key] = value.unpack()

+         return d

+ 

+     @staticmethod

+     def _variant_str(s):

+         if not isinstance(s, str):

+             raise TypeError ("Only strings are supported for scalars")

+ 

+         return GLib.Variant('s', s)

+ 

+     @classmethod

+     def _variant_list(cls, l):

+         l_variant = list()

+         for item in l:

+             if type(item) == str:

+                 l_variant.append(ModuleMetadata._variant_str(item))

+             elif type(item) == list:

+                 l_variant.append(ModuleMetadata._variant_list(item))

+             elif type(item) == dict:

+                 l_variant.append(ModuleMetadata._variant_dict(item))

+             else:

+                 raise TypeError("Cannot convert unknown type")

+         return GLib.Variant('av', l_variant)

+ 

+     @classmethod

+     def _variant_dict_values(cls, d):

+         if not isinstance(d, dict):

+             raise TypeError("Only dictionaries are supported for mappings")

+ 

+         d_variant = dict()

+         for k, v in d.items():

+             if type(v) == str:

+                 d_variant[k] = cls._variant_str(v)

+                 pass

+             elif type(v) == list:

+                 d_variant[k] = cls._variant_list(v)

+             elif type(v) == dict:

+                 d_variant[k] = cls._variant_dict(v)

+             else:

+                 raise TypeError("Cannot convert unknown type")

+         return d_variant

+ 

+     @classmethod

+     def _variant_dict(cls, d):

+         if not isinstance(d, dict):

+             raise TypeError("Only dictionaries are supported for mappings")

+ 

+         d_variant = cls._variant_dict_values(d)

+ 

+         return GLib.Variant('a{sv}', d_variant)

  

      @xmd.setter

      def xmd(self, d):

          if not isinstance(d, dict):

              raise TypeError("xmd: data type not supported")

-         self._xmd = d

+ 

+         xmd = self._variant_dict_values(d)

+ 

+         self.module.set_xmd(xmd)

  

      @property

      def profiles(self):

          """A dictionary property representing the module profiles."""

-         return self._profiles

+         return {k: ModuleProfile._new_from_native(native_profile=v,

+                                                   bound=True, parent=self)

+                 for k, v in self.module.get_profiles().items()}

  

      @profiles.setter

      def profiles(self, d):
@@ -842,63 +670,83 @@ 

          for k, v in d.items():

              if not isinstance(k, str) or not isinstance(v, ModuleProfile):

                  raise TypeError("profiles: data type not supported")

-         self._profiles = d

+ 

+         # ensure keys and names in profiles agree

+         for k, v in d.items():

+             v.name = k

+ 

+         self.module.set_profiles({k: v._to_native() for k, v in d.items()})

+ 

+     @property

+     def tree_objs(self):

+         if not hasattr(self, '_tree_objs'):

+             self._tree_objs = {

+                 'api': ModuleAPI(parent=self),

+                 'filter': ModuleFilter(parent=self),

+                 'buildopts': ModuleBuildopts(parent=self),

+                 'components': ModuleComponents(parent=self),

+                 'artifacts': ModuleArtifacts(parent=self),

+             }

+         return self._tree_objs

  

      @property

      def api(self):

          """A ModuleAPI instance representing the module's public API."""

-         return self._api

+         return self.tree_objs['api']

  

      @api.setter

      def api(self, o):

          if not isinstance(o, ModuleAPI):

              raise TypeError("api: data type not supported")

-         self._api = o

+ 

+         self.tree_objs['api'].replace(o)

  

      @property

      def filter(self):

          """A ModuleFilter instance representing the module's filter."""

-         return self._filter

+         return self.tree_objs['filter']

  

      @filter.setter

      def filter(self, o):

          if not isinstance(o, ModuleFilter):

              raise TypeError("filter: data type not supported")

-         self._filter = o

+ 

+         self.tree_objs['filter'].replace(o)

  

      @property

      def buildopts(self):

          """A ModuleBuildopts instance representing the additional

          module components build options.

          """

-         return self._buildopts

+         return self.tree_objs['buildopts']

  

      @buildopts.setter

      def buildopts(self, o):

          if not isinstance(o, ModuleBuildopts):

              raise TypeError("buildopts: data type not supported")

-         self._buildopts = o

+ 

+         self.tree_objs['buildopts'].replace(o)

  

      @property

      def components(self):

          """A ModuleComponents instance property representing the components

          defining the module.

          """

-         return self._components

+         return self.tree_objs['components']

  

      @components.setter

      def components(self, o):

          if not isinstance(o, ModuleComponents):

              raise TypeError("components: data type not supported")

-         self._components = o

+         self.tree_objs['components'].replace(o)

  

      @property

      def artifacts(self):

          """A ModuleArtifacts instance representing the module's artifacts."""

-         return self._artifacts

+         return self.tree_objs['artifacts']

  

      @artifacts.setter

      def artifacts(self, o):

          if not isinstance(o, ModuleArtifacts):

              raise TypeError("artifacts: data type not supported")

-         self._artifacts = o

+         self.tree_objs['artifacts'].replace(o)

file modified
+14 -51
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,58 +22,22 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- supported_content = ( "rpms", )

- 

- class ModuleAPI(object):

-     """Class representing a particular module API."""

+ from .legacy_wrappers import TreeObj, RPMsMixin

  

-     def __init__(self):

-         """Creates a new ModuleAPI instance."""

-         self.rpms = set()

- 

-     def __repr__(self):

-         return ("<ModuleAPI: "

-                 "rpms: {0}>").format(

-                         repr(sorted(self.rpms))

-                         )

  

-     def __bool__(self):

-         return True if self.rpms else False

+ supported_content = ('rpms',)

  

-     __nonzero__ = __bool__

  

-     @property

-     def rpms(self):

-         """A set of binary RPM packages defining this module's API."""

-         return self._rpms

- 

-     @rpms.setter

-     def rpms(self, ss):

-         if not isinstance(ss, set):

-             raise TypeError("api.rpms: data type not supported")

-         for v in ss:

-             if not isinstance(v, str):

-                 raise TypeError("api.rpms: data type not supported")

-         self._rpms = ss

- 

-     def add_rpm(self, s):

-         """Adds a binary RPM package to the API set.

+ class ModuleAPI(TreeObj, RPMsMixin):

+     """Class representing a particular module API."""

  

-         :param str s: Binary RPM package name

-         """

-         if not isinstance(s, str):

-             raise TypeError("api.add_rpm: data type not supported")

-         self._rpms.add(s)

+     tree_identity = 'api'

  

-     def del_rpm(self, s):

-         """Removes the supplied package name from the API package set.

+     def __repr__(self):

+         return "<ModuleAPI: rpms: {!r}>".format(sorted(self.rpms))

  

-         :param str s: Binary RPM package name

-         """

-         if not isinstance(s, str):

-             raise TypeError("api.del_rpm: data type not supported")

-         self._rpms.discard(s)

+     def get_rpm_set(self):

+         return self.parent.module.get_rpm_api()

  

-     def clear_rpms(self):

-         """Clear the API binary RPM package set."""

-         self._rpms.clear()

+     def set_rpm_set(self, ss):

+         self.parent.module.set_rpm_api(ss)

file modified
+19 -50
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016, 2017  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,58 +22,28 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- supported_content = ( "rpms", )

- 

- class ModuleArtifacts(object):

-     """Class representing a particular module artifacts."""

- 

-     def __init__(self):

-         """Creates a new ModuleArtifacts instance."""

-         self.rpms = set()

- 

-     def __repr__(self):

-         return ("<ModuleArtifacts: "

-                 "rpms: {0}>").format(

-                         repr(sorted(self.rpms))

-                         )

+ from .legacy_wrappers import TreeObj, RPMsMixin

  

-     def __bool__(self):

-         return True if self.rpms else False

  

-     __nonzero__ = __bool__

+ supported_content = ("rpms",)

  

-     @property

-     def rpms(self):

-         """A set of NEVRAs listing this module's RPM artifacts."""

-         return self._rpms

  

-     @rpms.setter

-     def rpms(self, ss):

-         if not isinstance(ss, set):

-             raise TypeError("artifacts.rpms: data type not supported")

-         for v in ss:

-             if not isinstance(v, str):

-                 raise TypeError("artifacts.rpms: data type not supported")

-         self._rpms = ss

+ class ModuleArtifacts(TreeObj, RPMsMixin):

+     """Class representing a particular module artifacts."""

  

-     def add_rpm(self, s):

-         """Adds an NEVRA to the artifact set.

+     tree_identity = 'artifacts'

  

-         :param str s: RPM NEVRA

-         """

-         if not isinstance(s, str):

-             raise TypeError("artifacts.add_rpm: data type not supported")

-         self._rpms.add(s)

+     def __init__(self, rpms=None, parent=None):

+         """Creates a new ModuleArtifacts instance."""

+         super(ModuleArtifacts, self).__init__(parent=parent)

+         if rpms:

+             self.rpms = rpms

  

-     def del_rpm(self, s):

-         """Removes the supplied NEVRA from the artifact set.

+     def __repr__(self):

+         return "<ModuleArtifacts: rpms: {!r}>".format(sorted(self.rpms))

  

-         :param str s: RPM NEVRA

-         """

-         if not isinstance(s, str):

-             raise TypeError("artifacts.del_rpm: data type not supported")

-         self._rpms.discard(s)

+     def get_rpm_set(self):

+         return self.parent.module.get_rpm_artifacts()

  

-     def clear_rpms(self):

-         """Clear the RPM artifacts set."""

-         self._rpms.clear()

+     def set_rpm_set(self, ss):

+         self.parent.module.set_rpm_artifacts(ss)

file modified
+22 -14
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016, 2017  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,22 +22,27 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- from modulemd.buildopts.rpms import ModuleBuildoptsRPMs

+ from ..legacy_wrappers import TreeObj

+ 

+ from .rpms import ModuleBuildoptsRPMs

+ 

  

- supported_content = ( "rpms", )

+ supported_content = ('rpms',)

  

- class ModuleBuildopts(object):

+ 

+ class ModuleBuildopts(TreeObj):

      """Class representing component build options."""

  

-     def __init__(self):

+     tree_props = ('rpms',)

+     tree_identity = 'buildopts'

+ 

+     def __init__(self, rpms=None, parent=None):

          """Creates a new ModuleBuildopts instance."""

-         self.rpms = ModuleBuildoptsRPMs()

+         super(ModuleBuildopts, self).__init__(parent=parent)

+         self._rpms = ModuleBuildoptsRPMs(parent=parent)

  

      def __repr__(self):

-         return ("<ModuleBuildopts: "

-                 "rpms: {0}>").format(

-                         repr(self.rpms)

-                         )

+         return "<ModuleBuildopts: rpms: {!r}>".format(self.rpms)

  

      def __bool__(self):

          return True if self.rpms else False
@@ -56,4 +60,8 @@ 

      def rpms(self, o):

          if not isinstance(o, ModuleBuildoptsRPMs):

              raise TypeError("buildopts.rpms: data type not supported")

-         self._rpms = o

+ 

+         if self.parent:

+             self._rpms.replace(o)

+         else:

+             self._rpms = o

file modified
+8 -9
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016, 2017  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,13 +22,13 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- import sys

+ from ..legacy_wrappers import TreeObj

+ 

  

- class ModuleBuildoptsBase(object):

-     """A base class for definining component build options."""

+ class ModuleBuildoptsBase(TreeObj):

+     """A base class for defining component build options.

  

-     def __init__(self):

-         """Creates a new ModuleBuildoptsBase instance."""

+     Nobody knows why it exists."""

  

      def __repr__(self):

          return ("<ModuleBuildoptsBase>")

file modified
+24 -13
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016, 2017  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,21 +22,20 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- from modulemd.buildopts.base import ModuleBuildoptsBase

+ from .base import ModuleBuildoptsBase

+ 

  

  class ModuleBuildoptsRPMs(ModuleBuildoptsBase):

      """A buildopts class for handling RPM content build options."""

  

-     def __init__(self, macros=""):

+     def __init__(self, macros="", parent=None):

          """Creates a new ModuleBuildoptsRPMs instance."""

-         super(ModuleBuildoptsRPMs, self).__init__()

+         super(ModuleBuildoptsRPMs, self).__init__(parent=parent)

+ 

          self.macros = macros

  

      def __repr__(self):

-         return ("<ModuleBuildoptsRPMs: "

-                 "macros: {0}>").format(

-                         repr(self.macros)

-                         )

+         return "<ModuleBuildoptsRPMs: macros: {!r}>".format(self.macros)

  

      def __bool__(self):

          return True if self.macros else False
@@ -49,10 +47,23 @@ 

          """A string property representing the additional RPM macros that

          should be used for this module build.

          """

-         return self._macros

+         if self.parent:

+             rpm_buildopts = self.parent.module.get_rpm_buildopts()

+             if rpm_buildopts:

+                 return rpm_buildopts['macros']

+             else:

+                 return ""

+         else:

+             return self._macros

  

      @macros.setter

      def macros(self, s):

          if not isinstance(s, str):

              raise TypeError("buildoptsrpm.macros: data type not supported")

-         self._macros = s

+ 

+         if self.parent:

+             opts = dict()

+             opts['macros'] = s

+             self.parent.module.set_rpm_buildopts(opts)

+         else:

+             self._macros = s

file modified
+139 -44
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,26 +22,32 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- from modulemd.components.module import ModuleComponentModule

- from modulemd.components.rpm import ModuleComponentRPM

+ from gi.repository import Modulemd

+ 

+ from ..legacy_wrappers import TreeObj

+ 

+ from .module import ModuleComponentModule

+ from .rpm import ModuleComponentRPM

+ 

  

- supported_content = ( "rpms", "modules", )

+ supported_content = ("rpms", "modules",)

  

- class ModuleComponents(object):

+ 

+ class ModuleComponents(TreeObj):

      """Class representing components of a module."""

  

-     def __init__(self):

+     tree_props = ('rpms', 'modules')

+     tree_identity = 'components'

+ 

+     def __init__(self, parent=None):

          """Creates a new ModuleComponents instance."""

-         self.modules = dict()

-         self.rpms = dict()

+         super(ModuleComponents, self).__init__(parent=parent)

+         self.rpms = {}

+         self.modules = {}

  

      def __repr__(self):

-         return ("<ModuleComponents: "

-                 "modules: {0}, "

-                 "rpms: {1}>").format(

-                         repr(sorted(self.modules)),

-                         repr(sorted(self.rpms))

-                         )

+         return "<ModuleComponents: modules: {!r}, rpms: {!r}>".format(

+             sorted(self.modules), sorted(self.rpms))

  

      def __bool__(self):

          return True if self.all else False
@@ -57,76 +62,166 @@ 

          ac.extend(self.modules.values())

          return ac

  

+     def _save_to_libmodulemd(self):

+         if getattr(self, 'parent', None) is not None:

+             # rpm packages

+             components = {}

+             for name, comp in self._rpms.items():

+                 native_comp = Modulemd.ComponentRpm()

+                 for attr in ('rationale', 'buildorder', 'repository', 'ref',

+                              'cache', 'arches', 'multilib'):

+                     val = getattr(comp, attr, None)

+                     if val is not None:

+                         if isinstance(val, set):

+                             ss = Modulemd.SimpleSet()

+                             ss.set(tuple(val))

+                             val = ss

+                         getattr(native_comp, 'set_' + attr)(val)

+                 components[name] = native_comp

+ 

+             self.parent.module.set_rpm_components(components)

+ 

+             # modules

+             components = {}

+             for name, comp in self._modules.items():

+                 native_comp = Modulemd.ComponentModule()

+                 for attr in ('rationale', 'buildorder', 'repository', 'ref'):

+                     val = getattr(comp, attr, None)

+                     if val is not None:

+                         if isinstance(val, set):

+                             ss = Modulemd.SimpleSet()

+                             ss.set(tuple(val))

+                             val = ss

+                         getattr(native_comp, 'set_' + attr)(val)

+                 components[name] = native_comp

+ 

+             self.parent.module.set_module_components(components)

+ 

      @property

      def rpms(self):

          """A dictionary of RPM components in this module.  The keys are SRPM

          names, the values ModuleComponentRPM instances.

          """

-         return self._rpms

+         if self.parent:

+             return {k: ModuleComponentRPM._new_from_native(v, parent=self.parent)

+                     for k, v in self.parent.module.get_rpm_components().items()}

+         else:

+             return self._rpms

  

      @rpms.setter

      def rpms(self, d):

          if not isinstance(d, dict):

              raise TypeError("components.rpms: data type not supported")

          for k, v in d.items():

-             if not isinstance(k, str) or not isinstance(v, ModuleComponentRPM):

-                 raise TypeError("components.rpms: data type not supported")

-         self._rpms = d

+             if not (isinstance(k, str)

+                     and isinstance(v, (ModuleComponentRPM,

+                                        Modulemd.ComponentRpm))):

+                 raise TypeError("components.rpms: data type not supported"

+                                 " ({!r}: {!r})".format(k, v))

+             if not self.parent and isinstance(v, Modulemd.ComponentRpm):

+                 d[k] = ModuleComponentRPM._new_from_native(v)

+             elif self.parent and isinstance(v, ModuleComponentRPM):

+                 d[k] = v._to_native()

+ 

+         if self.parent:

+             self.parent.module.set_rpm_components(d)

+         else:

+             self._rpms = d

  

      def add_rpm(self, name, rationale, buildorder=0,

              repository="", ref="", cache="", arches=set(), multilib=set()):

          """Adds an RPM to the set of module components."""

-         component = ModuleComponentRPM(name, rationale)

-         component.buildorder = buildorder

-         component.repository = repository

-         component.ref = ref

-         component.cache = cache

-         component.arches = arches

-         component.multilib = multilib

-         self._rpms[name] = component

+         component = ModuleComponentRPM(name=name, rationale=rationale,

+                                        buildorder=buildorder,

+                                        repository=repository, ref=ref,

+                                        cache=cache, arches=arches,

+                                        multilib=multilib,

+                                        parent=self.parent)

+ 

+         if self.parent:

+             rpms = self.rpms

+             rpms[name] = component

+             self.rpms = rpms

+         else:

+             self._rpms[name] = component

  

      def del_rpm(self, s):

          """Removes the supplied RPM from the set of module components."""

          if not isinstance(s, str):

              raise TypeError("components.del_rpm: data type not supported")

-         if s in self._rpms:

-             del self._rpms[s]

+         if self.parent:

+             rpms = self.rpms

+             rpms.pop(s, None)

+             self.rpms = rpms

+         else:

+             try:

+                 del self._rpms[s]

+             except KeyError:

+                 pass

  

      def clear_rpms(self):

          """Clear the RPM component dictionary."""

-         self._rpms.clear()

+         self.rpms = {}

  

      @property

      def modules(self):

          """A dictionary of module-type components in this module.  The keys are

          module names, the values ModuleComponentModule instances.

          """

-         return self._modules

+         if self.parent:

+             return {k: ModuleComponentModule._new_from_native(v, parent=self.parent)

+                     for k, v in

+                     self.parent.module.get_module_components().items()}

+         else:

+             return self._modules

  

      @modules.setter

      def modules(self, d):

          if not isinstance(d, dict):

              raise TypeError("components.modules: data type not supported")

          for k, v in d.items():

-             if not isinstance(k, str) or not isinstance(v, ModuleComponentModule):

-                 raise TypeError("components.modules: data type not supported")

-         self._modules = d

+             if not (isinstance(k, str)

+                     and isinstance(v, (ModuleComponentModule,

+                                        Modulemd.ComponentModule))):

+                 raise TypeError("components.modules: data type not supported"

+                                 " ({!r}: {!r})".format(k, v))

+             if not self.parent and isinstance(v, Modulemd.ComponentModule):

+                 d[k] = ModuleComponentModule._new_from_native(k, v)

+             elif self.parent and isinstance(v, ModuleComponentModule):

+                 d[k] = v._to_native()

+ 

+         if self.parent:

+             self.parent.module.set_module_components(d)

+         else:

+             self._modules = d

  

      def add_module(self, name, rationale, buildorder=0,

              repository="", ref=""):

-         component = ModuleComponentModule(name, rationale)

-         component.buildorder = buildorder

-         component.repository = repository

-         component.ref = ref

-         self._modules[name] = component

+         component = ModuleComponentModule(name=name, rationale=rationale,

+                                           buildorder=buildorder,

+                                           repository=repository, ref=ref)

+ 

+         if self.parent:

+             modules = self.modules

+             modules[name] = component

+             self.modules = modules

+         else:

+             self._modules[name] = component

  

      def del_module(self, s):

          """Removes the supplied module from the set of module components."""

          if not isinstance(s, str):

              raise TypeError("components.del_module: data type not supported")

-         if s in self._modules:

-             del self._modules[s]

+         if self.parent:

+             modules = self.modules

+             modules.pop(s, None)

+             self.modules = modules

+         else:

+             try:

+                 del self._modules[s]

+             except KeyError:

+                 pass

  

      def clear_modules(self):

          """Clear the module-type component dictionary."""

-         self._modules.clear()

+         self.modules = {}

file modified
+59 -23
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -25,66 +24,103 @@ 

  

  import sys

  

+ from ..legacy_wrappers import TreeObj

+ 

+ 

  if sys.version_info > (3,):

-     long = int

+     integer_types = (int,)

+ else:

+     integer_types = (int, long)

  

- class ModuleComponentBase(object):

+ 

+ class ModuleComponentBase(TreeObj):

      """A base class for definining module component types."""

  

-     def __init__(self, name, rationale, buildorder=0):

+     tree_props = ('name', 'rationale', 'buildorder')

+ 

+     # set this to a child class of Modulemd.Component

+     component_class = None

+ 

+     def __init__(self, name, rationale, buildorder=0, parent=None):

          """Creates a new ModuleComponentBase instance."""

+         super(ModuleComponentBase, self).__init__(parent=parent)

+         if parent:

+             self.component = self.component_class()

          self.name = name

          self.rationale = rationale

          self.buildorder = buildorder

  

      def __repr__(self):

-         return ("<ModuleComponentBase: "

-                 "name: {0}, "

-                 "rationale: {1}, "

-                 "buildorder: {2}>").format(

-                         repr(self.name),

-                         repr(self.rationale),

-                         repr(self.buildorder)

-                         )

+         return ("<ModuleComponentBase: name: {!r}, rationale: {!r},"

+                 " buildorder: {!r}>").format(self.name, self.rationale,

+                                              self.buildorder)

  

      def __bool__(self):

          return True if (self.name or self.rationale or self.buildorder) else False

  

      __nonzero__ = __bool__

  

+     def __eq__(self, other):

+         if type(self) != type(other):

+             return False

+ 

+         return (self.name == other.name

+                 and self.rationale == other.rationale

+                 and self.buildorder == other.buildorder)

+ 

      @property

      def name(self):

          """A string property representing the component name."""

-         return self._name

+         if self.parent:

+             return self.component.get_name()

+         else:

+             return self._name

  

      @name.setter

      def name(self, s):

          if not isinstance(s, str):

              raise TypeError("componentbase.name: data type not supported")

-         self._name = s

+ 

+         if self.parent:

+             self.component.set_name(s)

+         else:

+             self._name = s

  

      @property

      def rationale(self):

          """A string property representing the rationale for the component

          inclusion in the module.

          """

-         return self._rationale

+         if self.parent:

+             return self.component.get_rationale()

+         else:

+             return self._rationale

  

      @rationale.setter

      def rationale(self, s):

          if not isinstance(s, str):

-             raise TypeError("componentbase.rationale: data type not supported")

-         self._rationale = s

+             raise TypeError("componentbase.rationale: data type not supported"

+                             " ({!r})".format(s))

+         if self.parent:

+             self.component.set_rationale(s)

+         else:

+             self._rationale = s

  

      @property

      def buildorder(self):

          """An integer property representing the buildorder index for this

          component.

          """

-         return self._buildorder

+         if self.parent:

+             return self.component.get_buildorder()

+         else:

+             return self._buildorder

  

      @buildorder.setter

      def buildorder(self, i):

-         if not isinstance(i, (int, long)):

+         if not isinstance(i, integer_types):

              raise TypeError("componentbase.buildorder: data type not supported")

-         self._buildorder = i

+         if self.parent:

+             self.component.set_buildorder(i)

+         else:

+             self._buildorder = i

file modified
+64 -25
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016, 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,60 +22,100 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- from modulemd.components.base import ModuleComponentBase

+ from gi.repository import Modulemd

+ 

+ from .base import ModuleComponentBase

+ 

  

  class ModuleComponentModule(ModuleComponentBase):

      """A component class for handling module-type content."""

  

+     tree_props = ('repository', 'ref')

+ 

+     component_class = Modulemd.ComponentModule

+ 

      def __init__(self, name, rationale, buildorder=0,

-             repository="", ref=""):

+                  repository="", ref="", parent=None):

          """Creates a new ModuleComponentModule instance."""

-         super(ModuleComponentModule, self).__init__(name, rationale, buildorder)

+         super(ModuleComponentModule, self).__init__(name=name,

+                                                     rationale=rationale,

+                                                     buildorder=buildorder,

+                                                     parent=parent)

          self.repository = repository

          self.ref = ref

  

+     @classmethod

+     def _new_from_native(cls, native_comp, parent=None):

+         return cls(native_comp.get_name(), native_comp.get_rationale(),

+                    buildorder=native_comp.get_buildorder() or 0,

+                    repository=native_comp.get_repository() or "",

+                    ref=native_comp.get_ref() or "",

+                    parent=parent)

+ 

+     def _to_native(self):

+         obj = self.component_class()

+         obj.set_name(self.name)

+         obj.set_rationale(self.rationale)

+         obj.set_buildorder(self.buildorder)

+         obj.set_repository(self.repository or None)

+         obj.set_ref(self.ref or None)

+         return obj

+ 

      def __repr__(self):

-         return ("<ModuleComponentModule: "

-                 "name: {0}, "

-                 "rationale: {1}, "

-                 "buildorder: {2}, "

-                 "repository: {3}, "

-                 "ref: {4}>").format(

-                         repr(self.name),

-                         repr(self.rationale),

-                         repr(self.buildorder),

-                         repr(self.repository),

-                         repr(self.ref)

-                         )

+         return ("<ModuleComponentModule: name: {!r}, rationale: {!r},"

+                 " buildorder: {!r}, repository: {!r}, ref: {!r}>").format(

+                     self.name, self.rationale, self.buildorder,

+                     self.repository, self.ref)

  

      def __bool__(self):

-         return True if (self.name or self.rationale or self.buildorder or

-                 self.repository or self.ref) else False

+         return True if (super(ModuleComponentModule, self).__bool()

+                         or self.repository or self.ref) else False

  

      __nonzero__ = __bool__

  

+     def __eq__(self, other):

+         if not super(ModuleComponentModule, self).__eq__(other):

+             return False

+ 

+         return (self.repository == other.repository

+                 and self.ref == other.ref)

+ 

      @property

      def repository(self):

          """A string property representing the VCS repository with the modulemd

          file and possibly other module data.

          """

-         return self._repository

+         if self.parent:

+             return self.component.get_repository()

+         else:

+             return self._repository

  

      @repository.setter

      def repository(self, s):

          if not isinstance(s, str):

              raise TypeError("componentmodule.repository: data type not supported")

-         self._repository = s

+ 

+         if self.parent:

+             self.component.set_repository(s)

+         else:

+             self._repository = s

  

      @property

      def ref(self):

          """A string property representing the particular repository commit

          hash, branch or tag name used in this module.

          """

-         return self._ref

+         if self.parent:

+             return self.component.get_ref()

+         else:

+             return self._ref

  

      @ref.setter

      def ref(self, s):

          if not isinstance(s, str):

              raise TypeError("componentmodule.ref: data type not supported")

-         self._ref = s

+ 

+         if self.parent:

+             self.component.set_ref(s)

+         else:

+             self._ref = s

file modified
+112 -37
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016, 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,86 +22,146 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- from modulemd.components.base import ModuleComponentBase

+ from gi.repository import Modulemd

+ 

+ from .base import ModuleComponentBase

+ 

  

  class ModuleComponentRPM(ModuleComponentBase):

      """A component class for handling RPM content."""

  

+     tree_props = ('repository', 'ref', 'cache', 'arches', 'multilib')

+ 

+     component_class = Modulemd.ComponentRpm

+ 

      def __init__(self, name, rationale, buildorder=0,

-             repository="", ref="", cache="", arches=set(), multilib=set()):

+                  repository="", ref="", cache="", arches=set(),

+                  multilib=set(), parent=None):

          """Creates a new ModuleComponentRPM instance."""

-         super(ModuleComponentRPM, self).__init__(name, rationale, buildorder)

+         super(ModuleComponentRPM, self).__init__(name=name,

+                                                  rationale=rationale,

+                                                  buildorder=buildorder,

+                                                  parent=parent)

          self.repository = repository

          self.ref = ref

          self.cache = cache

          self.arches = arches

          self.multilib = multilib

  

+     @classmethod

+     def _new_from_native(cls, native_comp, parent=None):

+         return cls(name=native_comp.get_name(),

+                    rationale=native_comp.get_rationale(),

+                    buildorder=native_comp.get_buildorder() or 0,

+                    repository=native_comp.get_repository() or "",

+                    ref=native_comp.get_ref() or "",

+                    cache=native_comp.get_cache() or "",

+                    arches=set(native_comp.get_arches().get()),

+                    multilib=set(native_comp.get_multilib().get()),

+                    parent=parent)

+ 

+     def _to_native(self):

+         obj = self.component_class()

+         obj.set_name(self.name)

+         obj.set_rationale(self.rationale)

+         obj.set_buildorder(self.buildorder)

+         obj.set_repository(self.repository or None)

+         obj.set_ref(self.ref or None)

+         obj.set_cache(self.cache or None)

+ 

+         ss = Modulemd.SimpleSet()

+         ss.set(list(self.arches))

+         obj.set_arches(ss)

+ 

+         ss = Modulemd.SimpleSet()

+         ss.set(list(self.multilib))

+         obj.set_multilib(ss)

+ 

+         return obj

+ 

      def __repr__(self):

-         return ("<ModuleComponentRPM: "

-                 "name: {0}, "

-                 "rationale: {1}, "

-                 "buildorder: {2}, "

-                 "repository: {3}, "

-                 "ref: {4}, "

-                 "cache: {5}, "

-                 "arches: {6}, "

-                 "multilib: {7}>").format(

-                         repr(self.name),

-                         repr(self.rationale),

-                         repr(self.buildorder),

-                         repr(self.repository),

-                         repr(self.ref),

-                         repr(self.cache),

-                         repr(sorted(self.arches)),

-                         repr(sorted(self.multilib))

-                         )

+         return ("<ModuleComponentRPM: name: {!r}, rationale: {!r},"

+                 " buildorder: {!r}, repository: {!r}, ref: {!r}, cache: {!r},"

+                 " arches: {!r}, multilib: {!r}>").format(

+                     self.name, self.rationale, self.buildorder,

+                     self.repository, self.ref, self.cache, sorted(self.arches),

+                     sorted(self.multilib))

  

      def __bool__(self):

-         return True if (self.name or self.rationale or self.buildorder or

+         return True if (super(ModuleComponentRPM, self).__bool__() or

                  self.repository or self.ref or self.cache or self.arches or

                  self.multilib) else False

  

      __nonzero__ = __bool__

  

+     def __eq__(self, other):

+         if not super(ModuleComponentRPM, self).__eq__(other):

+             return False

+ 

+         return (self.repository == other.repository

+                 and self.ref == other.ref

+                 and self.cache == other.cache

+                 and self.arches == other.arches

+                 and self.multilib == other.multilib)

+ 

      @property

      def repository(self):

          """A string property representing the VCS repository with the RPM SPEC

          file, patches and other package data.

          """

-         return self._repository

+         if self.parent:

+             return self.component.get_repository()

+         else:

+             return self._repository

  

      @repository.setter

      def repository(self, s):

          if not isinstance(s, str):

-             raise TypeError("componentrpm.repository: data type not supported")

-         self._repository = s

+             raise TypeError("componentrpm.repository: data type not supported"

+                             " ({!r}".format(s))

+ 

+         if self.parent:

+             self.component.set_repository(s)

+         else:

+             self._repository = s

  

      @property

      def ref(self):

          """A string property representing the particular repository commit

          hash, branch or tag name used in this module.

          """

-         return self._ref

+         if self.parent:

+             return self.component.get_ref()

+         else:

+             return self._ref

  

      @ref.setter

      def ref(self, s):

          if not isinstance(s, str):

              raise TypeError("componentrpm.ref: data type not supported")

-         self._ref = s

+         if self.parent:

+             self.component.set_ref(s)

+         else:

+             self._ref = s

  

      @property

      def cache(self):

          """A string property representing the URL to the lookaside cache where

          this packages' sources are stored.

          """

-         return self._cache

+         if self.parent:

+             return self.component.get_cache()

+         else:

+             return self._cache

  

      @cache.setter

      def cache(self, s):

          if not isinstance(s, str):

              raise TypeError("componentrpm.cache: data type not supported")

-         self._cache = s

+         if self.parent:

+             self.component.set_cache(s)

+         else:

+             self._cache = s

  

      @property

      def arches(self):
@@ -110,7 +169,10 @@ 

          empty set equals to the package being available on all supported

          architectures.

          """

-         return self._arches

+         if self.parent:

+             return set(self.component.get_arches().get())

+         else:

+             return self._arches

  

      @arches.setter

      def arches(self, ss):
@@ -119,7 +181,12 @@ 

          for v in ss:

              if not isinstance(v, str):

                  raise TypeError("componentrpm.arches: data type not supported")

-         self._arches = ss

+         if self.parent:

+             _ss = Modulemd.SimpleSet()

+             _ss.set(list(ss))

+             self.component.set_arches(_ss)

+         else:

+             self._arches = ss

  

      @property

      def multilib(self):
@@ -127,7 +194,10 @@ 

          What this actually means varies from architecture to architecture.  An

          empty set is equal to no multilib.

          """

-         return self._multilib

+         if self.parent:

+             return set(self.component.get_multilib().get())

+         else:

+             return self._multilib

  

      @multilib.setter

      def multilib(self, ss):
@@ -136,4 +206,9 @@ 

          for v in ss:

              if not isinstance(v, str):

                  raise TypeError("componentrpm.multilib: data type not supported")

-         self._multilib = ss

+         if self.parent:

+             _ss = Modulemd.SimpleSet()

+             _ss.set(list(ss))

+             self.component.set_multilib(_ss)

+         else:

+             self._multilib = ss

file modified
+14 -51
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,58 +22,22 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- supported_content = ( "rpms", )

- 

- class ModuleFilter(object):

-     """Class representing a particular module filter."""

+ from .legacy_wrappers import TreeObj, RPMsMixin

  

-     def __init__(self):

-         """Creates a new ModuleFilter instance."""

-         self.rpms = set()

- 

-     def __repr__(self):

-         return ("<ModuleFilter: "

-                 "rpms: {0}>").format(

-                         repr(sorted(self.rpms))

-                         )

  

-     def __bool__(self):

-         return True if self.rpms else False

+ supported_content = ("rpms",)

  

-     __nonzero__ = __bool__

  

-     @property

-     def rpms(self):

-         """A set of binary RPM packages defining this module's filter."""

-         return self._rpms

- 

-     @rpms.setter

-     def rpms(self, ss):

-         if not isinstance(ss, set):

-             raise TypeError("filter.rpms: data type not supported")

-         for v in ss:

-             if not isinstance(v, str):

-                 raise TypeError("filter.rpms: data type not supported")

-         self._rpms = ss

- 

-     def add_rpm(self, s):

-         """Adds a binary RPM package to the filter set.

+ class ModuleFilter(TreeObj, RPMsMixin):

+     """Class representing a particular module filter."""

  

-         :param str s: Binary RPM package name

-         """

-         if not isinstance(s, str):

-             raise TypeError("filter.add_rpm: data type not supported")

-         self._rpms.add(s)

+     tree_identity = 'filter'

  

-     def del_rpm(self, s):

-         """Removes the supplied package name from the filter package set.

+     def __repr__(self):

+         return "<ModuleFilter: rpms: {!r}>".format(sorted(self.rpms))

  

-         :param str s: Binary RPM package name

-         """

-         if not isinstance(s, str):

-             raise TypeError("filter.del_rpm: data type not supported")

-         self._rpms.discard(s)

+     def get_rpm_set(self):

+         return self.parent.module.get_rpm_filter()

  

-     def clear_rpms(self):

-         """Clear the filter binary RPM package set."""

-         self._rpms.clear()

+     def set_rpm_set(self, ss):

+         self.parent.module.set_rpm_filter(ss)

@@ -0,0 +1,171 @@ 

+ # -*- coding: utf-8 -*-

+ 

+ 

+ # Copyright © 2016 - 2018 Red Hat, Inc.

+ #

+ # Permission is hereby granted, free of charge, to any person obtaining a copy

+ # of this software and associated documentation files (the "Software"), to deal

+ # in the Software without restriction, including without limitation the rights

+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+ # copies of the Software, and to permit persons to whom the Software is

+ # furnished to do so, subject to the following conditions:

+ #

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

+ #

+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

+ # SOFTWARE.

+ 

+ 

+ import inspect

+ 

+ from gi.repository import Modulemd

+ 

+ 

+ class _TreeObjBase(object):

+ 

+     # The defining properties of the particular tree object. Set this in child

+     # classes or a mixin.

+     tree_props = ()

+ 

+     # The name of the particular tree object. Set this in concrete child

+     # classes.

+     tree_identity = None

+ 

+ 

+ class TreeObj(_TreeObjBase):

+     """Class representing a part of the tree for the legacy Python API."""

+ 

+     def __init__(self, parent=None, **kwargs):

+         # self.parent is r/o

+         self._parent = parent

+         super(TreeObj, self).__init__(**kwargs)

+ 

+     def __bool__(self):

+         return True

+ 

+     def __nonzero__(self):

+         return self.__bool__()

+ 

+     @property

+     def parent(self):

+         return self._parent

+ 

+     def replace(self, o):

+         if not isinstance(o, type(self)):

+             raise TypeError(

+                 "{}: data type not supported".format(type(o).__name__))

+ 

+         props = set()

+         for base in inspect.getmro(type(self)):

+             props.update(getattr(base, 'tree_props', ()))

+ 

+         for prop in props:

+             setattr(self, prop, getattr(o, prop))

+ 

+ 

+ class RPMsMixin(_TreeObjBase):

+     """Mixin supporting an 'rpms' property as a set.

+ 

+     Needs get/set_rpm_set() implemented in child classes."""

+ 

+     tree_props = ('rpms',)

+ 

+     def __init__(self, rpms=None, **kwargs):

+         if rpms is None:

+             rpms = set()

+         super(RPMsMixin, self).__init__(**kwargs)

+         self.rpms = rpms

+ 

+     def __bool__(self):

+         return (super(RPMsMixin, self).__bool__()

+                 or True if self.rpms else False)

+ 

+     def get_rpm_set(self):

+         """Dispatch to native method for getting RPM sets.

+ 

+         This will be something like self.parent.module.get_rpm_*() or an

+         appropriate method on the associated native object."""

+         raise NotImplementedError()

+ 

+     def set_rpm_set(self, ss):

+         """Dispatch to native method for setting RPM sets.

+ 

+         This will be something like self.parent.module.set_rpm_*() or an

+         appropriate method on the associated native object."""

+         raise NotImplementedError()

+ 

+     @property

+     def rpms(self):

+         """A set of binary RPM packages defining this module's API."""

+         if self.parent:

+             return set(self.get_rpm_set().get())

+         else:

+             return self._rpms

+ 

+     @rpms.setter

+     def rpms(self, ss):

+         if ss is not None:

+             if not isinstance(ss, set):

+                 raise TypeError("{}.rpms: data type not supported:"

+                                 " {!r} (must be a set)".format(

+                                     self.tree_identity, ss))

+             for v in ss:

+                 if not isinstance(v, str):

+                     raise TypeError("{}.rpms: data type not supported:"

+                                     " {!r} (elements must be strings)".format(

+                                         self.tree_identity, v))

+ 

+         if self.parent:

+             if ss is not None:

+                 _ss = Modulemd.SimpleSet()

+                 _ss.set(list(ss))

+             else:

+                 _ss = None

+             self.set_rpm_set(_ss)

+         else:

+             self._rpms = ss

+ 

+     def add_rpm(self, s):

+         """Adds a binary RPM package to the API set.

+ 

+         :param str s: Binary RPM package name

+         """

+         if not isinstance(s, str):

+             raise TypeError("{}.add_rpm: data type not supported".format(

+                 self.tree_identity))

+ 

+         if self.parent:

+             rpms = self.rpms

+             rpms.add(s)

+             self.rpms = rpms

+         else:

+             self._rpms.add(s)

+ 

+     def del_rpm(self, s):

+         """Removes the supplied package name from the package set.

+ 

+         :param str s: Binary RPM package name

+         """

+         if not isinstance(s, str):

+             raise TypeError("api.del_rpm: data type not supported")

+ 

+         if self.parent:

+             rpms = self.rpms

+             rpms.discard(s)

+             self.rpms = rpms

+         else:

+             self._rpms.discard(s)

+ 

+     def clear_rpms(self):

+         """Clear the API binary RPM package set."""

+ 

+         if hasattr(self, 'parent'):

+             self.rpms = set()

+         else:

+             self._rpms.clear()

file modified
+97 -55
@@ -1,7 +1,6 @@ 

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -10,8 +9,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -23,73 +22,116 @@ 

  #

  # Written by Petr Šabata <contyk@redhat.com>

  

- supported_content = ( "rpms", )

+ from gi.repository import Modulemd

+ 

+ from .legacy_wrappers import TreeObj, RPMsMixin

+ 

  

- class ModuleProfile(object):

+ supported_content = ("rpms",)

+ 

+ 

+ class ModuleProfile(TreeObj, RPMsMixin):

      """Class representing a particular module profile."""

  

-     def __init__(self):

+     tree_props = ('name',)

+ 

+     @property

+     def tree_identity(self):

+         return "profile<{}>".format(getattr(self, 'name', '<unknown>'))

+ 

+     def __init__(self, name=None, description=None, rpms=None, bound=False,

+                  parent=None):

          """Creates a new ModuleProfile instance."""

-         self.description = ""

-         self.rpms = set()

+         if parent:

+             self.profile = Modulemd.Profile()

+         else:

+             assert not bound

+ 

+         self.bound = False

+ 

+         super(ModuleProfile, self).__init__(rpms=rpms, parent=parent)

+ 

+         self.name = name

+         self.description = description

+         self.bound = bound

+         self._save_to_collection()

  

      def __repr__(self):

-         return ("<ModuleProfile: "

-                 "description: {0}, "

-                 "rpms: {1}>").format(

-                         repr(self.description),

-                         repr(sorted(self.rpms))

-                         )

+         return ("<ModuleProfile: name: {!r}, description: {!r},"

+                 " rpms: {!r}>").format(self.name, self.description,

+                                        sorted(self.rpms))

  

      def __bool__(self):

-         return True if (self.description or self.rpms) else False

+         return bool(super(ModuleProfile, self).__bool__() or self.description)

  

      __nonzero__ = __bool__

  

+     @classmethod

+     def _new_from_native(cls, native_profile, bound=False, parent=None):

+         obj = cls(name=native_profile.get_name(),

+                    description=native_profile.get_description(),

+                    rpms=set(native_profile.get_rpms().get()), bound=bound,

+                    parent=parent)

+         return obj

+ 

+     def _to_native(self):

+         obj = Modulemd.Profile()

+         obj.set_name(self.name)

+         obj.set_description(self.description)

+ 

+         if self.rpms is not None:

+             ss = Modulemd.SimpleSet()

+             ss.set(list(self.rpms))

+             obj.set_rpms(ss)

+ 

+         return obj

+ 

+     def _save_to_collection(self):

+         if self.parent and self.bound:

+             profiles = self.parent.module.get_profiles()

+             profiles[self.name] = self._to_native()

+             self.parent.module.set_profiles(profiles)

+ 

+     def get_rpm_set(self):

+         return self.profile.get_rpms()

+ 

+     def set_rpm_set(self, ss):

+         self.profile.set_rpms(ss)

+         self._save_to_collection()

+ 

+     @property

+     def name(self):

+         """A string property representing the name of the profile."""

+         if self.parent:

+             return self.profile.get_name()

+         else:

+             return self._name

+ 

+     @name.setter

+     def name(self, s):

+         if s is not None and not isinstance(s, str):

+             raise TypeError("profile.name: data type not supported")

+         if self.parent:

+             self.profile.set_name(s)

+             self._save_to_collection()

+         else:

+             self._name = s

+ 

      @property

      def description(self):

          """A string property representing a detailed description of the

          profile."""

-         return self._description

+         if self.parent:

+             return self.profile.get_description()

+         else:

+             return self._description

  

      @description.setter

      def description(self, s):

-         if not isinstance(s, str):

+         if s is not None and not isinstance(s, str):

              raise TypeError("profile.description: data type not supported")

-         self._description = s

- 

-     @property

-     def rpms(self):

-         """A set of binary RPM packages included in this profile."""

-         return self._rpms

- 

-     @rpms.setter

-     def rpms(self, ss):

-         if not isinstance(ss, set):

-             raise TypeError("profile.rpms: data type not supported")

-         for v in ss:

-             if not isinstance(v, str):

-                 raise TypeError("profile.rpms: data type not supported")

-         self._rpms = ss

- 

-     def add_rpm(self, s):

-         """Adds a binary RPM package to the profile set.

- 

-         :param str s: Binary RPM package name

-         """

-         if not isinstance(s, str):

-             raise TypeError("profile.add_rpm: data type not supported")

-         self._rpms.add(s)

- 

-     def del_rpm(self, s):

-         """Removes the supplied package name from the profile package set.

- 

-         :param str s: Binary RPM package name

-         """

-         if not isinstance(s, str):

-             raise TypeError("profile.del_rpm: data type not supported")

-         self._rpms.discard(s)

- 

-     def clear_rpms(self):

-         """Clear the profile binary RPM package set."""

-         self._rpms.clear()

+         if self.parent:

+             self.profile.set_description(s)

+             self._save_to_collection()

+         else:

+             self._description = s

@@ -0,0 +1,104 @@ 

+ # This file merely contains test data, don't use it as a reference.

+ document: modulemd

+ version: 1

+ data:

+     name: foo

+     stream: stream-name

+     version: 20160927144203

+     context: c0ffee43

+     arch: x86_64

+     summary: An example module

+     description: >-

+         A module for the demonstration of the metadata format. Also,

+         the obligatory lorem ipsum dolor sit amet goes right here.

+     eol: 2077-10-23

+     servicelevels:

+         security:

+             eol: 2019-03-30

+         features:

+             eol: 2018-12-31

+     license:

+         module:

+             - MIT

+         content:

+             - Beerware

+             - GPLv2+

+             - zlib

+     xmd:

+         some_key: some_data

+     dependencies:

+         buildrequires:

+             platform: and-its-stream-name

+             extra-build-env: and-its-stream-name-too

+         requires:

+             platform: and-its-stream-name

+     references:

+         community: http://www.example.com/

+         documentation: http://www.example.com/

+         tracker: http://www.example.com/

+     profiles:

+         default:

+             rpms:

+                 - bar

+                 - bar-extras

+                 - baz

+         container:

+             rpms:

+                 - bar

+                 - bar-devel

+         minimal:

+             description: Minimal profile installing only the bar package.

+             rpms:

+                 - bar

+         buildroot:

+             rpms:

+                 - bar-devel

+         srpm-buildroot:

+             rpms:

+                 - bar-extras

+     api:

+         rpms:

+             - bar

+             - bar-extras

+             - bar-devel

+             - baz

+             - xxx

+     filter:

+         rpms:

+             - baz-nonfoo

+     buildopts:

+         rpms:

+             macros: |

+                 %demomacro 1

+                 %demomacro2 %{demomacro}23

+     components:

+         rpms:

+             bar:

+                 rationale: We need this to demonstrate stuff.

+                 repository: https://pagure.io/bar.git

+                 cache: https://example.com/cache

+                 ref: 26ca0c0

+             baz:

+                 rationale: This one is here to demonstrate other stuff.

+             xxx:

+                 rationale: xxx demonstrates arches and multilib.

+                 arches: [ i686, x86_64 ]

+                 multilib: [ x86_64 ]

+             xyz:

+                 rationale: xyz is a bundled dependency of xxx.

+                 buildorder: 10

+         modules:

+             includedmodule:

+                 rationale: Included in the stack, just because.

+                 repository: https://pagure.io/includedmodule.git

+                 ref: somecoolbranchname

+                 buildorder: 100

+     artifacts:

+         rpms:

+             - bar-0:1.23-1.module_deadbeef.x86_64

+             - bar-devel-0:1.23-1.module_deadbeef.x86_64

+             - bar-extras-0:1.23-1.module_deadbeef.x86_64

+             - baz-0:42-42.module_deadbeef.x86_64

+             - xxx-0:1-1.module_deadbeef.x86_64

+             - xxx-0:1-1.module_deadbeef.i686

+             - xyz-0:1-1.module_deadbeef.x86_64

file modified
+4 -5
@@ -1,8 +1,7 @@ 

- #/usr/bin/python3

+ #!/usr/bin/python3

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -11,8 +10,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

@@ -1,8 +1,7 @@ 

- #/usr/bin/python3

+ #!/usr/bin/python3

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -11,8 +10,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

file modified
+79 -8
@@ -1,8 +1,7 @@ 

- #/usr/bin/python3

+ #!/usr/bin/python3

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016, 2017 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -11,8 +10,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -35,40 +34,100 @@ 

  

  from modulemd import ModuleMetadata, dump_all, load_all

  

- class TestIO(unittest.TestCase):

+ TEST_YAML = os.path.join(DIR, "test.yaml")

+ 

+ 

+ class _TestIOBase(unittest.TestCase):

      maxDiff = None  # display diff when a test fails

  

      @classmethod

      def setUpClass(cls):

          cls.mmd = ModuleMetadata()

  

+ 

+ class TestIOMMDLoads(_TestIOBase):

+ 

      def test_load_spec(self):

-         self.mmd.load("spec.yaml")

+         self.mmd.load(TEST_YAML)

+ 

+ 

+ class TestIO(_TestIOBase):

+ 

+     @classmethod

+     def setUpClass(cls):

+         super(TestIO, cls).setUpClass()

+         cls.mmd.load(TEST_YAML)

+ 

+     def test_mdversion(self):

          self.assertEqual(self.mmd.mdversion, 1)

+ 

+     def test_name(self):

          self.assertEqual(self.mmd.name, "foo")

+ 

+     def test_stream(self):

          self.assertEqual(self.mmd.stream, "stream-name")

+ 

+     def test_version(self):

          self.assertEqual(self.mmd.version, 20160927144203)

+ 

+     def test_context(self):

          self.assertEqual(self.mmd.context, "c0ffee43")

+ 

+     def test_arch(self):

          self.assertEqual(self.mmd.arch, "x86_64")

+ 

+     def test_summary(self):

          self.assertEqual(self.mmd.summary, "An example module")

+ 

+     def test_description(self):

          self.assertEqual(self.mmd.description, "A module for the demonstration of the metadata format. Also, the obligatory lorem ipsum dolor sit amet goes right here.")

+ 

+     def test_eol(self):

          self.assertEqual(self.mmd.eol, datetime.date(2077, 10, 23))

+         securitylevel = self.mmd.servicelevels['security']

+         securityeol = datetime.date(securitylevel.get_eol().get_year(),

+                                     securitylevel.get_eol().get_month(),

+                                     securitylevel.get_eol().get_day())

+         self.assertEqual(securityeol, datetime.date(2019, 3, 30))

+         featurelevel = self.mmd.servicelevels['features']

+         featureeol = datetime.date(featurelevel.get_eol().get_year(),

+                                    featurelevel.get_eol().get_month(),

+                                    featurelevel.get_eol().get_day())

+         self.assertEqual(featureeol, datetime.date(2018, 12, 31))

+ 

+     def test_module_licenses(self):

          self.assertSetEqual(self.mmd.module_licenses, set(["MIT"]))

+ 

+     def test_content_licenses(self):

          self.assertSetEqual(self.mmd.content_licenses,

                  set(["Beerware", "GPLv2+", "zlib"]))

+ 

+     def test_xmd(self):

          self.assertEqual(self.mmd.xmd, {'some_key': 'some_data'})

+ 

+     def test_buildrequires(self):

          self.assertDictEqual(self.mmd.buildrequires,

                  {

                      "platform" : "and-its-stream-name",

                      "extra-build-env" : "and-its-stream-name-too"

                  })

+ 

+     def test_requires(self):

          self.assertDictEqual(self.mmd.requires,

                  {

                      "platform" : "and-its-stream-name"

                  })

+ 

+     def test_community(self):

          self.assertEqual(self.mmd.community, "http://www.example.com/")

+ 

+     def test_documentation(self):

          self.assertEqual(self.mmd.documentation, "http://www.example.com/")

+ 

+     def test_tracker(self):

          self.assertEqual(self.mmd.tracker, "http://www.example.com/")

+ 

+     def test_profiles(self):

          self.assertSetEqual(set(self.mmd.profiles.keys()),

                  set(["default", "minimal", "container", "buildroot",

                      "srpm-buildroot"]))
@@ -78,10 +137,16 @@ 

                  "Minimal profile installing only the bar package.")

          self.assertSetEqual(self.mmd.profiles["minimal"].rpms,

                  set(["bar"]))

+ 

+     def test_api(self):

          self.assertSetEqual(self.mmd.api.rpms,

                  set(["bar", "bar-extras", "bar-devel", "baz", "xxx"]))

+ 

+     def test_filter(self):

          self.assertSetEqual(self.mmd.filter.rpms,

                  set(["baz-nonfoo"]))

+ 

+     def test_components_rpms(self):

          self.assertSetEqual(set(self.mmd.components.rpms.keys()),

                  set(["bar", "baz", "xxx", "xyz"]))

          self.assertEqual(self.mmd.components.rpms["bar"].rationale,
@@ -104,6 +169,8 @@ 

                  "xyz is a bundled dependency of xxx.")

          self.assertEqual(self.mmd.components.rpms["xyz"].buildorder,

                  10)

+ 

+     def test_components_modules(self):

          self.assertSetEqual(set(self.mmd.components.modules),

                  set(["includedmodule"]))

          self.assertEqual(self.mmd.components.modules["includedmodule"].rationale,
@@ -114,6 +181,8 @@ 

                  "somecoolbranchname")

          self.assertEqual(self.mmd.components.modules["includedmodule"].buildorder,

                  100)

+ 

+     def test_artifacts_rpms(self):

          self.assertSetEqual(self.mmd.artifacts.rpms,

                  set(["bar-0:1.23-1.module_deadbeef.x86_64",

                       "bar-devel-0:1.23-1.module_deadbeef.x86_64",
@@ -122,11 +191,13 @@ 

                       "xxx-0:1-1.module_deadbeef.x86_64",

                       "xxx-0:1-1.module_deadbeef.i686",

                       "xyz-0:1-1.module_deadbeef.x86_64"]))

+ 

+     def test_buildopts_rpms_macros(self):

          self.assertEqual(self.mmd.buildopts.rpms.macros,

                  "%demomacro 1\n%demomacro2 %{demomacro}23\n")

  

      def test_reload(self):

-         self.mmd.load("spec.yaml")

+         self.mmd.load(TEST_YAML)

          first = repr(self.mmd)

          self.mmd.dump("testdump.yaml")

          self.mmd = ModuleMetadata()

@@ -1,8 +1,7 @@ 

- #/usr/bin/python3

+ #!/usr/bin/python3

  # -*- coding: utf-8 -*-

  

- 

- # Copyright (c) 2016  Red Hat, Inc.

+ # Copyright © 2016 - 2018 Red Hat, Inc.

  #

  # Permission is hereby granted, free of charge, to any person obtaining a copy

  # of this software and associated documentation files (the "Software"), to deal
@@ -11,8 +10,8 @@ 

  # copies of the Software, and to permit persons to whom the Software is

  # furnished to do so, subject to the following conditions:

  #

- # The above copyright notice and this permission notice shall be included in all

- # copies or substantial portions of the Software.

+ # The above copyright notice and this permission notice shall be included in

+ # all copies or substantial portions of the Software.

  #

  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -77,6 +76,9 @@ 

      def test_eol_type(self):

          self.assertRaises(TypeError, setattr, self.mmd, "eol", 0)

  

+     def test_servicelevels_type(self):

+         self.assertRaises(TypeError, setattr, self.mmd, "servicelevels", 0)

+ 

      def test_module_license_type(self):

          self.assertRaises(TypeError, setattr, self.mmd, "module_licenses", 0)

  

file removed
-144
@@ -1,144 +0,0 @@ 

- %global _pkgdescription A python library for manipulation of the proposed module metadata format.

- 

- %if 0%{?fedora} > 21 || 0%{?rhel} > 7

- %global with_python3 1

- %else

- %global with_python3 0

- %endif

- 

- %{!?__python2: %global __python2 /usr/bin/python2}

- %{!?py2_build: %global py2_build %{expand: CFLAGS="%{optflags}" %{__python2} setup.py %{?py_setup_args} build --executable="%{__python2} -s"}}

- %{!?py2_install: %global py2_install %{expand: CFLAGS="%{optflags}" %{__python2} setup.py %{?py_setup_args} install -O1 --skip-build --root %{buildroot}}} 

- 

- Name:           modulemd

- Version:        1.1.0

- Release:        1%{?dist}

- Summary:        Module metadata manipulation library

- License:        MIT

- URL:            https://pagure.io/modulemd

- Source0:        https://files.pythonhosted.org/packages/source/m/%{name}/%{name}-%{version}.tar.gz

- BuildArch:      noarch

- BuildRequires:  python2-devel

- BuildRequires:  PyYAML

- BuildRequires:  python-setuptools

- %if 0%{?with_python3}

- BuildRequires:  python3-devel

- BuildRequires:  python3-PyYAML

- BuildRequires:  python3-setuptools

- %endif

- 

- %description

- %{_pkgdescription}

- 

- %package -n python2-%{name}

- Summary:        %{summary}

- Requires:       PyYAML

- Provides:       python-%{name} = %{version}-%{release}

- 

- %description -n python2-%{name}

- %{_pkgdescription}

- 

- These are python2 bindings.

- 

- %if 0%{?with_python3}

- %package -n python3-%{name}

- Summary:        %{summary}

- Requires:       python3-PyYAML

- 

- %description -n python3-%{name}

- %{_pkgdescription}

- 

- These are python3 bindings.

- %endif

- 

- %prep

- %setup -q

- 

- %build

- %py2_build

- %if 0%{?with_python3}

- %py3_build

- %endif

- 

- %install

- %py2_install

- %if 0%{?with_python3}

- %py3_install

- %endif

- 

- %check

- %{__python2} setup.py test

- %if 0%{?with_python3}

- %{__python3} setup.py test

- %endif

- 

- %files -n python2-%{name}

- %doc README.rst spec.yaml

- %license LICENSE

- %{python2_sitelib}/*

- 

- %if 0%{?with_python3}

- %files -n python3-%{name}

- %doc README.rst spec.yaml

- %license LICENSE

- %{python3_sitelib}/*

- %endif

- 

- %changelog

- * Mon Feb 13 2017 Petr Šabata <contyk@redhat.com> - 1.1.0-1

- - 1.1.0 bump

- 

- * Tue Nov 08 2016 Petr Šabata <contyk@redhat.com> - 1.0.2-1

- - 1.0.2 bugfix release

- 

- * Tue Nov 08 2016 Petr Šabata <contyk@redhat.com> - 1.0.1-1

- - First official release

- 

- * Fri Sep 23 2016 Jan Kaluza <jkaluza@redhat.com> - 0-13

- - build without Python 3 support on older distributions that don't have it

- 

- * Fri Sep 16 2016 Petr Šabata <contyk@redhat.com> - 0-12

- - Update modlint's runtime dependencies

- - modlint shouldn't install the README and spec.yaml files

- 

- * Wed Aug 03 2016 Jan Kaluza <jkaluza@redhat.com> - 0-11

- - Add modlint subpackage

- 

- * Tue Jul 19 2016 Petr Šabata <contyk@redhat.com> - 0-10

- - Don't fail validation tests

- - Use safe_dump() for dumping YAMLs

- 

- * Tue Jul 12 2016 Petr Šabata <contyk@redhat.com> - 0-9

- - Profiles now support description

- - The components section is now truly optional

- 

- * Sat Jul 09 2016 Petr Šabata <contyk@redhat.com> - 0-8

- - rpms.update_package() now allows updating just one property

- 

- * Thu Jun 30 2016 Petr Šabata <contyk@redhat.com> - 0-7

- - Adding support for binary package filters

- 

- * Tue Jun 21 2016 Petr Šabata <contyk@redhat.com> - 0-6

- - New metadata format

-    - module use-case profiles are now supported

- 

- * Tue Jun 14 2016 Petr Šabata <contyk@redhat.com> - 0-5

- - Rename metadata.yaml to spec.yaml

- 

- * Tue Jun 14 2016 Petr Šabata <contyk@redhat.com> - 0-4

- - New metadata format

-    - rpms/api now holds the module RPM-defined API

- 

- * Fri Jun 10 2016 Petr Šabata <contyk@redhat.com> - 0-3

- - New metadata format

-   - rpms/dependencies defaults to False

-   - rpms/fulltree was removed

- 

- * Thu May 12 2016 Petr Šabata <contyk@redhat.com> - 0-2

- - New metadata format, rationale is now required

- 

- * Fri May 06 2016 Petr Šabata <contyk@redhat.com> - 0-1

- - New metadata format

- 

- * Mon May 02 2016 Petr Šabata <contyk@redhat.com> - 0-0

- - This package was build automatically.

file modified
+3 -3
@@ -9,9 +9,9 @@ 

  

  setup(

      name = "modulemd",

-     version = "1.3.3",

-     author = "Petr Šabata",

-     author_email = "contyk@redhat.com",

+     version = "1.4.0",

+     author = "Stephen Gallagher",

+     author_email = "sgallagh@redhat.com",

      description = ("A python library for manipulation of the proposed "

          "module metadata format."),

      license = "MIT",

file removed
-272
@@ -1,272 +0,0 @@ 

- # Document type identifier

- document: modulemd

- # Module metadata format version

- version: 1

- data:

-     # Module name, optional

-     # Typically filled in by the buildsystem, using the VCS repository

-     # name as the name of the module.

-     name: foo

-     # Module update stream, optional

-     # Typically filled in by the buildsystem, using the VCS branch name

-     # as the name of the stream.

-     stream: stream-name

-     # Module version, integer, optional, cannot be negative

-     # Typically filled in by the buildsystem, using the VCS commit

-     # timestamp.  Module version defines upgrade path for the particular

-     # update stream.

-     version: 20160927144203

-     # Module context flag, optional

-     # The context flag serves to distinguish module builds with the

-     # same name, stream and version and plays an important role in

-     # future automatic module stream name expansion.

-     # Filled in by the buildsystem.  A short hash of the module's name,

-     # stream, version and its expanded runtime dependencies.

-     context: c0ffee43

-     # Module artifact architecture, optional

-     # Contains a string describing the module's artifacts' main hardware

-     # architecture compatibility, distinguishing the module artifact,

-     # e.g. a repository, from others with the same name, stream, version and

-     # context.  This is not a generic hardware family (i.e. basearch).

-     # Examples: i386, i486, armv7hl, x86_64

-     # Filled in by the buildsystem during the compose stage.

-     arch: x86_64

-     # A short summary describing the module, required

-     summary: An example module

-     # A verbose description of the module, required

-     description: >-

-         A module for the demonstration of the metadata format. Also,

-         the obligatory lorem ipsum dolor sit amet goes right here.

-     # The end of life, aka Best Before, optional.

-     # A UTC date in the ISO 8601 format signifying the day when this

-     # module goes EOL, i.e. starting the day below, this module won't

-     # receive any more updates.  Typically defined in an external data

-     # source and filled in by the buildsystem.

-     eol: 2077-10-23

-     # Module and content licenses in the Fedora license identifier

-     # format, required

-     license:

-         # Module license, required

-         # This list covers licenses used for the module metadata and

-         # possibly other files involved in the creation of this specific

-         # module.

-         module:

-             - MIT

-         # Content license, optional

-         # A list of licenses used by the packages in the module.

-         # This should be populated by build tools, not the module author.

-         content:

-             - Beerware

-             - GPLv2+

-             - zlib

-     # Extensible metadata block

-     # A dictionary of user-defined keys and values.

-     # Optional.  Defaults to an empty dictionary.

-     xmd:

-         some_key: some_data

-     # Module dependencies, if any.  Optional.

-     # TODO: Provides, conflicts, obsoletes, recommends, etc.

-     #       Do we even need those?

-     # TODO: Stream name globbing or regular expression support

-     dependencies:

-         # Build dependencies of this module, optional

-         # Keys are module names, values are the stream names

-         # These modules define the buildroot for this module

-         buildrequires:

-             platform: and-its-stream-name

-             extra-build-env: and-its-stream-name-too

-         # Run-time dependencies of this module, optional

-         # Keys are module names, values are their stream names

-         requires:

-             platform: and-its-stream-name

-     # References to external resources, typically upstream, optional

-     references:

-         # Upstream community website, if it exists, optional

-         community: http://www.example.com/

-         # Upstream documentation, if it exists, optional

-         documentation: http://www.example.com/

-         # Upstream bug tracker, if it exists, optional

-         tracker: http://www.example.com/

-     # Profiles define the end user's use cases for the module. They consist of

-     # package lists of components to be installed by default if the module is

-     # enabled. The keys are the profile names and contain package lists by

-     # component type. There are several profiles defined below. Suggested

-     # behavior for package managers is to just enable repository for selected

-     # module. Then users are able to install packages on their own. If they

-     # select a specific profile, the package manager should install all

-     # packages of that profile.

-     # Optional, defaults to no profile definitions.

-     profiles:

-         # The default profile, used unless any other profile was selected.

-         # Optional, defaults to empty lists.

-         default:

-             rpms:

-                 - bar

-                 - bar-extras

-                 - baz

-         # Defines a set of packages which are meant to be installed inside

-         # container image artifact.

-         # Optional.

-         container:

-             rpms:

-                 - bar

-                 - bar-devel

-         # This profile provides minimal set of packages providing functionality

-         # of this module. This is meant to be used on target systems where size

-         # of the distribution is a real concern.

-         # Optional.

-         minimal:

-             # A verbose description of the module, optional

-             description: Minimal profile installing only the bar package.

-             rpms:

-                 - bar

-         # A set of packages which should be installed into the buildroot of a

-         # module which depends on this module.  Specifically, it is used to

-         # flesh out the build group in koji.

-         # Optional.

-         buildroot:

-             rpms:

-                 - bar-devel

-         # Very similar to the buildroot profile above, this is used by the

-         # build system to specify any additional packages which should be

-         # installed during the buildSRPMfromSCM step in koji.

-         # Optional.

-         srpm-buildroot:

-             rpms:

-                 - bar-extras

-     # Module API

-     # Optional, defaults to no API.

-     api:

-         # The module's public RPM-level API.

-         # A list of binary RPM names that are considered to be the

-         # main and stable feature of the module; binary RPMs not listed

-         # here are considered "unsupported" or "implementation details".

-         # In the example here we don't list the xyz package as it's only

-         # included as a dependency of xxx.  However, we list a subpackage

-         # of bar, bar-extras.

-         # Optional, defaults to an empty list.

-         rpms:

-             - bar

-             - bar-extras

-             - bar-devel

-             - baz

-             - xxx

-     # Module component filters

-     # Optional, defaults to no filters.

-     filter:

-         # RPM names not to be included in the module.

-         # By default, all built binary RPMs are included.  In the example

-         # we exclude a subpackage of bar, bar-nonfoo from our module.

-         # Optional, defaults to an empty list.

-         rpms:

-             - baz-nonfoo

-     # Component build options

-     # Additional per component type module-wide build options.

-     # Optional

-     buildopts:

-         # RPM-specific build options

-         # Optional

-         rpms:

-             # Additional macros that should be defined in the

-             # RPM buildroot, appended to the default set.  Care should be

-             # taken so that the newlines are preserved.  Literal style

-             # block is recommended, with or without the trailing newline.

-             # Optional

-             macros: |

-                 %demomacro 1

-                 %demomacro2 %{demomacro}23

-     # Functional components of the module, optional

-     components:

-         # RPM content of the module, optional

-         # Keys are the VCS/SRPM names, values dictionaries holding

-         # additional information.

-         rpms:

-             bar:

-                 # Why is this component present.

-                 # A simple, free-form string.

-                 # Required.

-                 rationale: We need this to demonstrate stuff.

-                 # Use this repository if it's different from the build

-                 # system configuration.

-                 # Optional.

-                 repository: https://pagure.io/bar.git

-                 # Use this lookaside cache if it's different from the

-                 # build system configuration.

-                 # Optional.

-                 cache: https://example.com/cache

-                 # Use this specific commit has, branch name or tag for

-                 # the build.  If ref is a branch name, the branch HEAD

-                 # will be used.  If no ref is given, the master branch

-                 # is assumed.

-                 # Optional.

-                 ref: 26ca0c0

-             # baz has no extra options

-             baz:

-                 rationale: This one is here to demonstrate other stuff.

-             xxx:

-                 rationale: xxx demonstrates arches and multilib.

-                 # xxx is only available on the listed architectures.

-                 # TODO: This needs a better definition of what the

-                 #       architectures here actually mean.

-                 # Optional, defaults to all available arches.

-                 arches: [ i686, x86_64 ]

-                 # A list of architectures with multilib

-                 # installs, i.e. both i686 and x86_64

-                 # versions will be installed on x86_64.

-                 # TODO: This needs to be reworked or dropped as it's

-                 #       not at all useful in the current state.

-                 # Optional, defaults to no multilib.

-                 multilib: [ x86_64 ]

-             xyz:

-                 rationale: xyz is a bundled dependency of xxx.

-                 # Build order group

-                 # When building, components are sorted by build order tag

-                 # and built in batches grouped by their buildorder value.

-                 # Built batches are then re-tagged into the buildroot.

-                 # Multiple components can have the same buildorder index

-                 # to map them into build groups.

-                 # Optional, defaults to zero.

-                 # Integer, negative values are allowed.

-                 # In this example, bar, baz and xxx are built first in

-                 # no particular order, then tagged into the buildroot,

-                 # then, finally, xyz is built.

-                 buildorder: 10

-         # Module content of this module

-         # Included modules are built in the shared buildroot, together with

-         # other included content.  Keys are module names, values additional

-         # component information.  Note this only includes components and their

-         # properties from the referenced module and doesn't inherit any

-         # additional module metadata such as the module's dependencies or

-         # component buildopts.  The included components are built in their

-         # defined buildorder as sub-build groups.

-         # Optional

-         modules:

-             includedmodule:

-                 # Why is this module included?

-                 # Required

-                 rationale: Included in the stack, just because.

-                 # Link to VCS repository that contains the modulemd file

-                 # if it differs from the buildsystem default configuration.

-                 # Optional.

-                 repository: https://pagure.io/includedmodule.git

-                 # See the rpms ref.

-                 ref: somecoolbranchname

-                 # See the rpms buildorder.

-                 buildorder: 100

-     # Artifacts shipped with this module

-     # This section lists binary artifacts shipped with the module, allowing

-     # software management tools to handle module bundles.  This section is

-     # populated by the module build system.

-     # Optional

-     artifacts:

-         # RPM artifacts shipped with this module

-         # A set of NEVRAs associated with this module.

-         # Optional

-         rpms:

-             - bar-0:1.23-1.module_deadbeef.x86_64

-             - bar-devel-0:1.23-1.module_deadbeef.x86_64

-             - bar-extras-0:1.23-1.module_deadbeef.x86_64

-             - baz-0:42-42.module_deadbeef.x86_64

-             - xxx-0:1-1.module_deadbeef.x86_64

-             - xxx-0:1-1.module_deadbeef.i686

-             - xyz-0:1-1.module_deadbeef.x86_64

file removed
-41
@@ -1,41 +0,0 @@ 

- # Tox (http://tox.testrun.org/) is a tool for running tests

- # in multiple virtualenvs. This configuration file will run the

- # test suite on all supported python versions. To use it, "pip install tox"

- # and then run "tox" from this directory.

- 

- [tox]

- envlist = py27, py35, coverage, flake8, bandit

- 

- [flake8]

- max-line-length = 100

- 

- [testenv]

- usedevelop = true

- deps = pytest

- commands =

-     py.test {posargs}

- 

- [testenv:coverage]

- basepython = python2

- deps =

-     {[testenv]deps}

-     coverage

- commands =

-     coverage run --parallel-mode -m pytest

-     coverage combine

-     coverage report --omit=.tox/* -m --skip-covered

- 

- [testenv:flake8]

- basepython = python3

- skip_install = true

- deps = flake8

- commands = flake8 --ignore E731 --exclude .tox,.git

- ignore_outcome = True

- 

- [testenv:bandit]

- basepython = python3

- skip_install = true

- deps = bandit

- commands =

-     /bin/bash -c "bandit -r -ll $(find . -mindepth 1 -maxdepth 1 ! -name \.\* -type d -o -name \*.py)"

- ignore_outcome = True

Additionally:

  • get rid of the outdated RPM spec file, spec.yaml (link to up-to-date copy in libmodulemd), tox tests (which don't seem to play well with gobject introspection)
  • tidy up some things

Hmm, bumping the version a second time is a bit excessive.

48 new commits added

  • link libmodulemd spec.yaml
  • instead of tox, describe running unit tests at least
  • using tox for testing doesn't work
  • document that modulemd is a wrapper
  • remove RPM spec file
  • remove spec comments and add explanation
  • move spec.yaml to tests and rename
  • update copyright terms
  • fix broken hash-bangs
  • use TreeObj/RPMsMixin based classes in ModuleMetadata
  • migrate ModuleProfile to TreeObj
  • migrate ModuleComponent* to TreeObj
  • migrate ModuleBuildopts* to TreeObj
  • migrate ModuleFilter to TreeObj/RPMsMixin
  • migrate ModuleArtifacts to TreeObj/RPMsMixin
  • migrate ModuleAPI to TreeObj/RPMsMixin
  • make variant*() methods real class/static methods
  • fix servicelevels property setter
  • allow unsetting eol date
  • reset tree new-style
  • pep8: remove unused imports, reformat some things
  • don't fake long type in Python 3
  • use relative imports within modulemd package
  • add TreeObj and RPMsMixin classes
  • reformat license and copyright headers
  • implement ModuleComponent(RPM|Module).eq()
  • components: cope with native objects in setters
  • only use libmodulemd functions in init()
  • convert ModuleComponents etc. to libmodulemd
  • don't overwrite self._buildopts on each access
  • make pyflakes complain less
  • import gobject-introspected modules early
  • split up monolithic test method
  • fix typo
  • update required libmodulemd version
  • Convert ModuleProfile to libmodulemd
  • Add support for securitylevels
  • Convert ModuleBuildopts to libmodulemd
  • Convert licenses to Modulemd
  • restore accidentally deleted repr() calls
  • Implement ModuleArtifacts
  • Implement ModuleFilter
  • Implement ModuleAPI
  • Parse xmd input into GLib.Variants
  • Load and dump using libmodulemd
  • First pass at updating the main modulemd.ModuleMetadata
  • Bump version to 1.4.0dev
  • Undo ordered dict code in prep for libmodulemd wrapper
6 years ago

@nphilipp - do you have a reviewer lined up for this? I know it is probably pending changes in the underlying libmodulemd being worked on by @sgallagh.

Should we seek out a specific reviewer for this?

(Getting this merged and released on top of the new v2 support in libmodulemd will (I think) block getting Factory serviced ported over.. that is, unless the libmodulemd lib itself provides its own compatible python interface.)

edit: that, in turn, will block things like stream expansion.

Libmodulemd does provide its own python interface, but it’s not compatible with the earlier one. We had created this wrapper to be a drop-in replacement, but the v2 format requires changing the API as well, so we are recommending that you port to the libmodulemd API.

Even if we update these patches, they won’t be a drop-in replacement because the handling for module dependencies have changed.

Yeah, last week we decided that while the wrapper as such is finished, maintaining it doesn't make much sense in the light of the API changes. Aw, shucks. ;)

Pull-Request has been closed by nphilipp

6 years ago