From 5803536ced962be465bd5ee0b2de664bf705311a Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 15:48:00 +0000
Subject: [PATCH 1/48] Undo ordered dict code in prep for libmodulemd wrapper


Revert "Preserve modulemd field ordering in dumps/dumps_all"

This reverts commit 362ffeb159728d72000f9eb2b29a9c6844097374.

Revert "Add internal method to dump into an OrderedDict"

This reverts commit 1a7b656e87b168dda74ceecc88ef81ca8ecb55f7.

---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 1d2c31e..86d43f1 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -40,7 +40,6 @@ Example usage:
     mmd.dump("out.yaml")
 """
 
-from collections import OrderedDict
 import sys
 import datetime
 import dateutil.parser
@@ -61,22 +60,6 @@ from modulemd.profile import ModuleProfile
 
 supported_mdversions = ( 1, )
 
-# 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)
-
-        value.append((node_key, node_value))
-
-    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
     into a list of ModuleMetadata instances.
@@ -115,8 +98,7 @@ def dumps_all(l):
 
     :param list l: List of ModuleMetadata instances
     """
-    return yaml.safe_dump_all([x._dumpd_ordered() for x in l],
-                              explicit_start=True)
+    return yaml.safe_dump_all([x.dumpd() for x in l], explicit_start=True)
 
 class ModuleMetadata(object):
     """Class representing the whole module."""
@@ -359,17 +341,17 @@ class ModuleMetadata(object):
         with open(f, "w") as outfile:
             outfile.write(data)
 
-    def _dumpd_ordered(self):
-        """Dumps the metadata into a OrderedDict.
+    def dumpd(self):
+        """Dumps the metadata into a dictionary.
 
-        :rtype: collections.OrderedDict
+        :rtype: dict
         """
-        doc = OrderedDict()
+        doc = dict()
         # header
         doc["document"] = "modulemd"
         doc["version"] = self.mdversion
         # data
-        d = OrderedDict()
+        d = dict()
         if self.name:
             d["name"] = self.name
         if self.stream:
@@ -384,18 +366,18 @@ class ModuleMetadata(object):
         d["description"] = self.description
         if self.eol:
             d["eol"] = str(self.eol)
-        d["license"] = OrderedDict()
+        d["license"] = dict()
         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()
+            d["dependencies"] = dict()
             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()
+            d["references"] = dict()
             if self.community:
                 d["references"]["community"] = self.community
             if self.documentation:
@@ -405,39 +387,39 @@ class ModuleMetadata(object):
         if self.xmd:
             d["xmd"] = self.xmd
         if self.profiles:
-            d["profiles"] = OrderedDict()
+            d["profiles"] = dict()
             for profile in self.profiles.keys():
                 if self.profiles[profile].description:
                     if profile not in d["profiles"]:
-                        d["profiles"][profile] = OrderedDict()
+                        d["profiles"][profile] = dict()
                     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] = dict()
                     d["profiles"][profile]["rpms"] = \
                         sorted(list(self.profiles[profile].rpms))
         if self.api:
-            d["api"] = OrderedDict()
+            d["api"] = dict()
             if self.api.rpms:
                 d["api"]["rpms"] = sorted(list(self.api.rpms))
         if self.filter:
-            d["filter"] = OrderedDict()
+            d["filter"] = dict()
             if self.filter.rpms:
                 d["filter"]["rpms"] = sorted(list(self.filter.rpms))
         if self.buildopts:
-            d["buildopts"] = OrderedDict()
+            d["buildopts"] = dict()
             if self.buildopts.rpms:
-                d["buildopts"]["rpms"] = OrderedDict()
+                d["buildopts"]["rpms"] = dict()
                 if self.buildopts.rpms.macros:
                     d["buildopts"]["rpms"]["macros"] = \
                             self.buildopts.rpms.macros
         if self.components:
-            d["components"] = OrderedDict()
+            d["components"] = dict()
             if self.components.rpms:
-                d["components"]["rpms"] = OrderedDict()
+                d["components"]["rpms"] = dict()
                 for p in self.components.rpms.values():
-                    extra = OrderedDict()
+                    extra = dict()
                     extra["rationale"] = p.rationale
                     if p.buildorder:
                         extra["buildorder"] = p.buildorder
@@ -453,9 +435,9 @@ class ModuleMetadata(object):
                         extra["multilib"] = sorted(list(p.multilib))
                     d["components"]["rpms"][p.name] = extra
             if self.components.modules:
-                d["components"]["modules"] = OrderedDict()
+                d["components"]["modules"] = dict()
                 for p in self.components.modules.values():
-                    extra = OrderedDict()
+                    extra = dict()
                     extra["rationale"] = p.rationale
                     if p.buildorder:
                         extra["buildorder"] = p.buildorder
@@ -465,40 +447,18 @@ class ModuleMetadata(object):
                         extra["ref"] = p.ref
                     d["components"]["modules"][p.name] = extra
         if self.artifacts:
-            d["artifacts"] = OrderedDict()
+            d["artifacts"] = dict()
             if self.artifacts.rpms:
                 d["artifacts"]["rpms"] = sorted(list(self.artifacts.rpms))
         doc["data"] = d
         return doc
 
-    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
-
     def dumps(self):
         """Dumps the metadata into a string.
 
         :rtype: str
         """
-        return yaml.safe_dump(self._dumpd_ordered(), default_flow_style=False)
+        return yaml.safe_dump(self.dumpd(), default_flow_style=False)
 
     @property
     def mdversion(self):

From f3a41cff7accbc3ded2343b86e2c138a54c7d6b9 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:35:06 +0000
Subject: [PATCH 2/48] Bump version to 1.4.0dev


---

diff --git a/setup.py b/setup.py
index f1f9fd3..1bbc51b 100755
--- a/setup.py
+++ b/setup.py
@@ -9,9 +9,9 @@ def read(f):
 
 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",

From b86cdee705898e7ceaf9efa9ad4ad6eb1538b82f Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:35:06 +0000
Subject: [PATCH 3/48] First pass at updating the main modulemd.ModuleMetadata


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 86d43f1..21e8e01 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -58,6 +58,11 @@ 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.1')
+from gi.repository import Modulemd
+
 supported_mdversions = ( 1, )
 
 def load_all(f):
@@ -107,30 +112,44 @@ class ModuleMetadata(object):
 
     def __init__(self):
         """Creates a new ModuleMetadata instance."""
-        self.mdversion = max(supported_mdversions)
-        self.name = ""
-        self.stream = ""
-        self.version = 0
-        self.context = ""
-        self.arch = ""
-        self.summary = ""
-        self.description = ""
-        self.eol = None
-        self.module_licenses = set()
-        self.content_licenses = set()
-        self.buildrequires = dict()
-        self.requires = dict()
-        self.community = ""
-        self.documentation = ""
-        self.tracker = ""
-        self.xmd = dict()
-        self.profiles = dict()
+        # Under the hood, we will use the ModulemdModule type
+        self.module = Modulemd.Module()
+
+        self.module.set_mdversion(max(supported_mdversions))
+        self.module.set_name("")
+        self.module.set_stream("")
+        self.module.set_version(0)
+        self.module.set_context("")
+        self.module.set_arch("")
+        self.module.set_summary("")
+        self.module.set_description("")
+        # Don't pre-set EOL, since we don't have a valid value yet
+        # The Modulemd.Module() default initializes it to an appropriate invalid
+        # date.
+        # self.module.set_eol()
+        self.module.set_module_licenses(Modulemd.SimpleSet())
+        self.module.set_content_licenses(Modulemd.SimpleSet())
+        self.module.set_buildrequires({})
+        self.module.set_requires({})
+        self.module.set_community("")
+        self.module.set_documentation("")
+        self.module.set_tracker("")
+        self.module.set_xmd({})
+        self.module.set_profiles({})
+        self.module.set_rpm_api(Modulemd.SimpleSet())
+        self.module.set_rpm_filter(Modulemd.SimpleSet())
+        self.module.set_rpm_buildopts({})
+        self.module.set_rpm_components({})
+        self.module.set_module_components({})
+        self.module.set_rpm_artifacts(Modulemd.SimpleSet())
+
         self.api = ModuleAPI()
         self.filter = ModuleFilter()
         self.buildopts = ModuleBuildopts()
         self.components = ModuleComponents()
         self.artifacts = ModuleArtifacts()
 
+
     def __repr__(self):
         return ("<ModuleMetadata: "
                 "mdversion: {0}, "
@@ -174,9 +193,6 @@ class ModuleMetadata(object):
                         repr(sorted(self.xmd)),
                         repr(sorted(self.profiles)),
                         repr(self.api),
-                        repr(self.filter),
-                        repr(self.components),
-                        repr(self.artifacts)
                 )
 
     def load(self, f):
@@ -184,9 +200,7 @@ class ModuleMetadata(object):
 
         :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.
@@ -469,7 +483,7 @@ class ModuleMetadata(object):
         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):
@@ -477,34 +491,34 @@ class ModuleMetadata(object):
             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):
@@ -512,72 +526,81 @@ class ModuleMetadata(object):
             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
+
+        d = GLib.Date.new_dmy(o.day, o.month, o.year)
+        self.module.set_eol(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):
@@ -586,7 +609,10 @@ class ModuleMetadata(object):
         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.
@@ -595,7 +621,9 @@ class ModuleMetadata(object):
         """
         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.
@@ -614,7 +642,7 @@ class ModuleMetadata(object):
     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):
@@ -623,7 +651,9 @@ class ModuleMetadata(object):
         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_module_licenses(simpleset)
 
     def add_content_license(self, s):
         """Adds a content license to the set.
@@ -632,7 +662,9 @@ class ModuleMetadata(object):
         """
         if not isinstance(s, str):
             raise TypeError("add_content_license: data type not supported")
-        self._content_licenses.add(s)
+        simpleset = self.module.get_module_licenses()
+        simpleset.add(s)
+        self.module.set_module_licenses(simpleset)
 
     def del_content_license(self, s):
         """Removes the supplied license from the content licenses set.
@@ -655,7 +687,7 @@ class ModuleMetadata(object):
         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):
@@ -664,7 +696,7 @@ class ModuleMetadata(object):
         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.
@@ -674,7 +706,9 @@ class ModuleMetadata(object):
         """
         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
 
@@ -685,12 +719,16 @@ class ModuleMetadata(object):
         """
         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):
@@ -700,7 +738,7 @@ class ModuleMetadata(object):
         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):
@@ -709,7 +747,7 @@ class ModuleMetadata(object):
         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.
@@ -719,7 +757,10 @@ class ModuleMetadata(object):
         """
         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
 
@@ -730,69 +771,83 @@ class ModuleMetadata(object):
         """
         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
 
     @xmd.setter
     def xmd(self, d):
         if not isinstance(d, dict):
             raise TypeError("xmd: data type not supported")
-        self._xmd = d
+
+        # TODO: implement this
+        return
+
+        self.module.set_xmd(d)
 
     @property
     def profiles(self):
         """A dictionary property representing the module profiles."""
+        # TODO: return the profiles as a dict of ModuleProfile types
+        return {}
+
         return self._profiles
 
     @profiles.setter

From 0574759fc5c733c3b99014fa0aad429e17693d22 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 4/48] Load and dump using libmodulemd


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 21e8e01..b66fc24 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -71,9 +71,15 @@ def load_all(f):
 
     :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
@@ -82,10 +88,13 @@ def loads_all(s):
     :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):
@@ -94,8 +103,11 @@ def dump_all(f, l):
     :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
@@ -103,7 +115,11 @@ def dumps_all(l):
 
     :param list l: List of ModuleMetadata instances
     """
-    return yaml.safe_dump_all([x.dumpd() 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."""
@@ -207,8 +223,7 @@ class ModuleMetadata(object):
 
         :param str s: Raw metadata in YAML
         """
-        yamld = yaml.safe_load(s)
-        self.loadd(yamld)
+        self.module = Modulemd.Module.new_from_string(f)
 
     def loadd(self, d):
         """Loads metadata from a dictionary.
@@ -216,263 +231,29 @@ class ModuleMetadata(object):
         :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)
+        self.module.dump(f)
 
     def dumpd(self):
         """Dumps the metadata into a dictionary.
 
         :rtype: dict
         """
-        doc = dict()
-        # header
-        doc["document"] = "modulemd"
-        doc["version"] = self.mdversion
-        # data
-        d = dict()
-        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"] = dict()
-        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"] = dict()
-            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"] = dict()
-            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"] = dict()
-            for profile in self.profiles.keys():
-                if self.profiles[profile].description:
-                    if profile not in d["profiles"]:
-                        d["profiles"][profile] = dict()
-                    d["profiles"][profile]["description"] = \
-                        str(self.profiles[profile].description)
-                if self.profiles[profile].rpms:
-                    if profile not in d["profiles"]:
-                        d["profiles"][profile] = dict()
-                    d["profiles"][profile]["rpms"] = \
-                        sorted(list(self.profiles[profile].rpms))
-        if self.api:
-            d["api"] = dict()
-            if self.api.rpms:
-                d["api"]["rpms"] = sorted(list(self.api.rpms))
-        if self.filter:
-            d["filter"] = dict()
-            if self.filter.rpms:
-                d["filter"]["rpms"] = sorted(list(self.filter.rpms))
-        if self.buildopts:
-            d["buildopts"] = dict()
-            if self.buildopts.rpms:
-                d["buildopts"]["rpms"] = dict()
-                if self.buildopts.rpms.macros:
-                    d["buildopts"]["rpms"]["macros"] = \
-                            self.buildopts.rpms.macros
-        if self.components:
-            d["components"] = dict()
-            if self.components.rpms:
-                d["components"]["rpms"] = dict()
-                for p in self.components.rpms.values():
-                    extra = dict()
-                    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"] = dict()
-                for p in self.components.modules.values():
-                    extra = dict()
-                    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"] = dict()
-            if self.artifacts.rpms:
-                d["artifacts"]["rpms"] = sorted(list(self.artifacts.rpms))
-        doc["data"] = d
-        return doc
+        return yaml.safe_load(self.dumps())
 
     def dumps(self):
         """Dumps the metadata into a string.
 
         :rtype: str
         """
-        return yaml.safe_dump(self.dumpd(), default_flow_style=False)
+        return self.module.dumps()
 
     @property
     def mdversion(self):

From 702aa41f5830d9fc5408ffeb4b6324afeebf716b Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 5/48] Parse xmd input into GLib.Variants


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index b66fc24..4330b3c 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -613,15 +613,59 @@ class ModuleMetadata(object):
             d[key] = value.unpack()
         return d
 
+    def _variant_str(s):
+        if not isinstance(s, str):
+            raise TypeError ("Only strings are supported for scalars")
+
+        return GLib.Variant('s', s)
+
+    def _variant_list(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);
+
+    def _variant_dict_values(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] = ModuleMetadata._variant_str(v);
+                pass
+            elif type(v) == list:
+                d_variant[k] = ModuleMetadata._variant_list(v);
+            elif type(v) == dict:
+                d_variant[k] = ModuleMetadata._variant_dict(v);
+            else:
+                raise TypeError ("Cannot convert unknown type")
+        return d_variant
+
+    def _variant_dict(d):
+        if not isinstance(d, dict):
+            raise TypeError ("Only dictionaries are supported for mappings")
+
+        d_variant = ModuleMetadata._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")
 
-        # TODO: implement this
-        return
+        xmd = ModuleMetadata._variant_dict_values(d)
 
-        self.module.set_xmd(d)
+        self.module.set_xmd(xmd)
 
     @property
     def profiles(self):

From 558f32928b27f9460e8458aeae00981fad036e7a Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 6/48] Implement ModuleAPI


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 4330b3c..69ccec7 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -159,7 +159,6 @@ class ModuleMetadata(object):
         self.module.set_module_components({})
         self.module.set_rpm_artifacts(Modulemd.SimpleSet())
 
-        self.api = ModuleAPI()
         self.filter = ModuleFilter()
         self.buildopts = ModuleBuildopts()
         self.components = ModuleComponents()
@@ -687,13 +686,19 @@ class ModuleMetadata(object):
     @property
     def api(self):
         """A ModuleAPI instance representing the module's public API."""
-        return self._api
+        api = ModuleAPI(parent=self)
+        api.rpms = set(self.module.get_rpm_api().get())
+        return api
 
     @api.setter
     def api(self, o):
         if not isinstance(o, ModuleAPI):
             raise TypeError("api: data type not supported")
-        self._api = o
+
+        rpms = Modulemd.SimpleSet()
+        rpms.set(list(o.rpms))
+
+        self.module.set_rpm_api(rpms)
 
     @property
     def filter(self):
diff --git a/modulemd/api.py b/modulemd/api.py
index 41bbb90..a48d848 100644
--- a/modulemd/api.py
+++ b/modulemd/api.py
@@ -25,12 +25,15 @@
 
 supported_content = ( "rpms", )
 
+from gi.repository import Modulemd
+
 class ModuleAPI(object):
     """Class representing a particular module API."""
 
-    def __init__(self):
+    def __init__(self, parent=None):
         """Creates a new ModuleAPI instance."""
         self.rpms = set()
+        self.parent = parent
 
     def __repr__(self):
         return ("<ModuleAPI: "
@@ -57,6 +60,11 @@ class ModuleAPI(object):
                 raise TypeError("api.rpms: data type not supported")
         self._rpms = ss
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_api(ss)
+
     def add_rpm(self, s):
         """Adds a binary RPM package to the API set.
 
@@ -66,6 +74,11 @@ class ModuleAPI(object):
             raise TypeError("api.add_rpm: data type not supported")
         self._rpms.add(s)
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_api(ss)
+
     def del_rpm(self, s):
         """Removes the supplied package name from the API package set.
 
@@ -75,6 +88,16 @@ class ModuleAPI(object):
             raise TypeError("api.del_rpm: data type not supported")
         self._rpms.discard(s)
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_api(ss)
+
     def clear_rpms(self):
         """Clear the API binary RPM package set."""
         self._rpms.clear()
+
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_api(ss)

From 7d2c497a1bef44bc377c34df3ce2bcae27e2e201 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 7/48] Implement ModuleFilter


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 69ccec7..b12d268 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -159,7 +159,6 @@ class ModuleMetadata(object):
         self.module.set_module_components({})
         self.module.set_rpm_artifacts(Modulemd.SimpleSet())
 
-        self.filter = ModuleFilter()
         self.buildopts = ModuleBuildopts()
         self.components = ModuleComponents()
         self.artifacts = ModuleArtifacts()
@@ -703,13 +702,19 @@ class ModuleMetadata(object):
     @property
     def filter(self):
         """A ModuleFilter instance representing the module's filter."""
-        return self._filter
+        filter = ModuleFilter(parent=self)
+        filter.rpms = set(self.module.get_rpm_filter().get())
+        return filter
 
     @filter.setter
     def filter(self, o):
         if not isinstance(o, ModuleFilter):
             raise TypeError("filter: data type not supported")
-        self._filter = o
+
+        rpms = Modulemd.SimpleSet()
+        rpms.set(list(o.rpms))
+
+        self.module.set_rpm_filter(rpms)
 
     @property
     def buildopts(self):
diff --git a/modulemd/filter.py b/modulemd/filter.py
index 232ad0d..b2cecd1 100644
--- a/modulemd/filter.py
+++ b/modulemd/filter.py
@@ -25,12 +25,15 @@
 
 supported_content = ( "rpms", )
 
+from gi.repository import Modulemd
+
 class ModuleFilter(object):
     """Class representing a particular module filter."""
 
-    def __init__(self):
+    def __init__(self, parent=None):
         """Creates a new ModuleFilter instance."""
         self.rpms = set()
+        self.parent = parent
 
     def __repr__(self):
         return ("<ModuleFilter: "
@@ -57,6 +60,11 @@ class ModuleFilter(object):
                 raise TypeError("filter.rpms: data type not supported")
         self._rpms = ss
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_filter(ss)
+
     def add_rpm(self, s):
         """Adds a binary RPM package to the filter set.
 
@@ -66,6 +74,11 @@ class ModuleFilter(object):
             raise TypeError("filter.add_rpm: data type not supported")
         self._rpms.add(s)
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_filter(ss)
+
     def del_rpm(self, s):
         """Removes the supplied package name from the filter package set.
 
@@ -75,6 +88,16 @@ class ModuleFilter(object):
             raise TypeError("filter.del_rpm: data type not supported")
         self._rpms.discard(s)
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_filter(ss)
+
     def clear_rpms(self):
         """Clear the filter binary RPM package set."""
         self._rpms.clear()
+
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_filter(ss)

From f974f7baf90fdcca2e5cce62efbb189320153aad Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 8/48] Implement ModuleArtifacts


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index b12d268..fb4fc22 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -161,7 +161,6 @@ class ModuleMetadata(object):
 
         self.buildopts = ModuleBuildopts()
         self.components = ModuleComponents()
-        self.artifacts = ModuleArtifacts()
 
 
     def __repr__(self):
@@ -745,10 +744,16 @@ class ModuleMetadata(object):
     @property
     def artifacts(self):
         """A ModuleArtifacts instance representing the module's artifacts."""
-        return self._artifacts
+        artifacts = ModuleArtifacts(parent=self)
+        artifacts.rpms = set(self.module.get_rpm_artifacts().get())
+        return artifacts
 
     @artifacts.setter
     def artifacts(self, o):
         if not isinstance(o, ModuleArtifacts):
             raise TypeError("artifacts: data type not supported")
-        self._artifacts = o
+
+        rpms = Modulemd.SimpleSet()
+        rpms.set(list(o.rpms))
+
+        self.module.set_rpm_artifacts(rpms)
diff --git a/modulemd/artifacts.py b/modulemd/artifacts.py
index 6d52a00..efdc228 100644
--- a/modulemd/artifacts.py
+++ b/modulemd/artifacts.py
@@ -25,12 +25,15 @@
 
 supported_content = ( "rpms", )
 
+from gi.repository import Modulemd
+
 class ModuleArtifacts(object):
     """Class representing a particular module artifacts."""
 
-    def __init__(self):
+    def __init__(self, parent=None):
         """Creates a new ModuleArtifacts instance."""
         self.rpms = set()
+        self.parent = parent
 
     def __repr__(self):
         return ("<ModuleArtifacts: "
@@ -57,6 +60,11 @@ class ModuleArtifacts(object):
                 raise TypeError("artifacts.rpms: data type not supported")
         self._rpms = ss
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_artifacts(ss)
+
     def add_rpm(self, s):
         """Adds an NEVRA to the artifact set.
 
@@ -66,6 +74,11 @@ class ModuleArtifacts(object):
             raise TypeError("artifacts.add_rpm: data type not supported")
         self._rpms.add(s)
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_artifacts(ss)
+
     def del_rpm(self, s):
         """Removes the supplied NEVRA from the artifact set.
 
@@ -75,6 +88,16 @@ class ModuleArtifacts(object):
             raise TypeError("artifacts.del_rpm: data type not supported")
         self._rpms.discard(s)
 
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_artifacts(ss)
+
     def clear_rpms(self):
         """Clear the RPM artifacts set."""
         self._rpms.clear()
+
+        if hasattr(self, 'parent'):
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            self.parent.module.set_rpm_artifacts(ss)

From 0c925d596e89545173e0ace3ffdccc57a2a0595f Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 9/48] restore accidentally deleted repr() calls


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index fb4fc22..d4c15de 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -206,6 +206,9 @@ class ModuleMetadata(object):
                         repr(sorted(self.xmd)),
                         repr(sorted(self.profiles)),
                         repr(self.api),
+                        repr(self.filter),
+                        repr(self.components),
+                        repr(self.artifacts)
                 )
 
     def load(self, f):

From 55891dbdb03844828082e3899ea89dfa08d0999c Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 10/48] Convert licenses to Modulemd


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index d4c15de..7429e3e 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -413,11 +413,13 @@ class ModuleMetadata(object):
         """
         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):
@@ -434,7 +436,7 @@ class ModuleMetadata(object):
                 raise TypeError("content_licenses: data type not supported")
         simpleset = Modulemd.SimpleSet()
         simpleset.set(list(ss))
-        self.module.set_module_licenses(simpleset)
+        self.module.set_content_licenses(simpleset)
 
     def add_content_license(self, s):
         """Adds a content license to the set.
@@ -443,9 +445,9 @@ class ModuleMetadata(object):
         """
         if not isinstance(s, str):
             raise TypeError("add_content_license: data type not supported")
-        simpleset = self.module.get_module_licenses()
+        simpleset = self.module.get_content_licenses()
         simpleset.add(s)
-        self.module.set_module_licenses(simpleset)
+        self.module.set_content_licenses(simpleset)
 
     def del_content_license(self, s):
         """Removes the supplied license from the content licenses set.
@@ -454,11 +456,13 @@ class ModuleMetadata(object):
         """
         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):

From be9d78d9892795cede47ea2ebf0a5e58aeb43bee Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 11/48] Convert ModuleBuildopts to libmodulemd


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 7429e3e..757fbd2 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -727,6 +727,7 @@ class ModuleMetadata(object):
         """A ModuleBuildopts instance representing the additional
         module components build options.
         """
+        self._buildopts = ModuleBuildopts(parent=self)
         return self._buildopts
 
     @buildopts.setter
diff --git a/modulemd/buildopts/__init__.py b/modulemd/buildopts/__init__.py
index 5aa7b73..9f4cc11 100644
--- a/modulemd/buildopts/__init__.py
+++ b/modulemd/buildopts/__init__.py
@@ -30,9 +30,11 @@ supported_content = ( "rpms", )
 class ModuleBuildopts(object):
     """Class representing component build options."""
 
-    def __init__(self):
+    def __init__(self, parent=None):
         """Creates a new ModuleBuildopts instance."""
-        self.rpms = ModuleBuildoptsRPMs()
+        self.rpms = ModuleBuildoptsRPMs(parent=parent)
+        self.parent = parent
+
 
     def __repr__(self):
         return ("<ModuleBuildopts: "
@@ -57,3 +59,8 @@ class ModuleBuildopts(object):
         if not isinstance(o, ModuleBuildoptsRPMs):
             raise TypeError("buildopts.rpms: data type not supported")
         self._rpms = o
+
+        if hasattr(self, 'parent') and self.parent is not None:
+            opts = dict()
+            opts['macros'] = o.macros
+            self.parent.set_buildopts(opts)
diff --git a/modulemd/buildopts/rpms.py b/modulemd/buildopts/rpms.py
index 908684b..6dc0640 100644
--- a/modulemd/buildopts/rpms.py
+++ b/modulemd/buildopts/rpms.py
@@ -28,10 +28,26 @@ from modulemd.buildopts.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__()
         self.macros = macros
+        self.parent = parent
+
+        # If macros were specified at initialization, update the
+        # module with it
+        if self.macros:
+            if hasattr(self, 'parent'):
+                opts = dict()
+                opts['macros'] = self.macros
+                self.parent.module.set_rpm_buildopts(opts)
+            return
+
+        if parent:
+            m = self.parent.module.get_rpm_buildopts()
+            if 'macros' in m:
+                self.macros = m['macros']
+
 
     def __repr__(self):
         return ("<ModuleBuildoptsRPMs: "
@@ -56,3 +72,8 @@ class ModuleBuildoptsRPMs(ModuleBuildoptsBase):
         if not isinstance(s, str):
             raise TypeError("buildoptsrpm.macros: data type not supported")
         self._macros = s
+
+        if hasattr(self, 'parent') and self.parent is not None:
+            opts = dict()
+            opts['macros'] = self.macros
+            self.parent.module.set_rpm_buildopts(opts)

From bde7c3cc98f52f3cba0ae3c2f53220fd9c570c5b Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 12/48] Add support for securitylevels


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 757fbd2..3f3ee5b 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -142,7 +142,7 @@ class ModuleMetadata(object):
         # Don't pre-set EOL, since we don't have a valid value yet
         # The Modulemd.Module() default initializes it to an appropriate invalid
         # date.
-        # self.module.set_eol()
+        self.module.set_servicelevels({})
         self.module.set_module_licenses(Modulemd.SimpleSet())
         self.module.set_content_licenses(Modulemd.SimpleSet())
         self.module.set_buildrequires({})
@@ -378,6 +378,16 @@ class ModuleMetadata(object):
         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):
+            self.module.set_servicelevels(d)
+
+    @property
     def module_licenses(self):
         """A set of strings, a property, representing the license terms
         of the module itself."""
diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py
index c420eb9..8912912 100644
--- a/modulemd/tests/test_io.py
+++ b/modulemd/tests/test_io.py
@@ -53,6 +53,16 @@ class TestIO(unittest.TestCase):
         self.assertEqual(self.mmd.summary, "An example module")
         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.")
         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))
         self.assertSetEqual(self.mmd.module_licenses, set(["MIT"]))
         self.assertSetEqual(self.mmd.content_licenses,
                 set(["Beerware", "GPLv2+", "zlib"]))
diff --git a/modulemd/tests/test_validation.py b/modulemd/tests/test_validation.py
index 19abc8f..563dd2a 100644
--- a/modulemd/tests/test_validation.py
+++ b/modulemd/tests/test_validation.py
@@ -77,6 +77,9 @@ class TestValidation(unittest.TestCase):
     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)
 
diff --git a/spec.yaml b/spec.yaml
index cf9b790..6b5b66b 100644
--- a/spec.yaml
+++ b/spec.yaml
@@ -43,6 +43,16 @@ data:
     # receive any more updates.  Typically defined in an external data
     # source and filled in by the buildsystem.
     eol: 2077-10-23
+    # Service levels, optional
+    # This is a dictionary of important dates (and possibly supplementary data
+    # in the future) that describes the end point of certain functionality,
+    # such as the date when the module will transition to "security fixes only"
+    # or go completely end-of-life
+    servicelevels:
+        security:
+            eol: 2019-03-30
+        features:
+            eol: 2018-12-31
     # Module and content licenses in the Fedora license identifier
     # format, required
     license:

From 42c6fc60998954feb577300f1d35a749bf26a008 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 13/48] Convert ModuleProfile to libmodulemd


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 3f3ee5b..a2a390c 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -685,9 +685,11 @@ class ModuleMetadata(object):
     def profiles(self):
         """A dictionary property representing the module profiles."""
         # TODO: return the profiles as a dict of ModuleProfile types
-        return {}
+        d = dict()
+        for k,v in self.module.get_profiles().items():
+            d[k] = ModuleProfile(profile=v, parent=self, name=k)
 
-        return self._profiles
+        return d
 
     @profiles.setter
     def profiles(self, d):
@@ -696,7 +698,20 @@ class ModuleMetadata(object):
         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
+
+        profiles = self.module.get_profiles()
+
+        for k,v in d.items():
+            profile = Modulemd.Profile()
+            if v.description:
+                profile.set_description(v.description)
+
+            ss = Modulemd.SimpleSet()
+            ss.set(list(v.rpms))
+
+            profile.set_rpms(ss)
+            profiles[k] = profile
+        self.module.set_profiles(profiles)
 
     @property
     def api(self):
diff --git a/modulemd/profile.py b/modulemd/profile.py
index 254ff76..9e2d2ad 100644
--- a/modulemd/profile.py
+++ b/modulemd/profile.py
@@ -25,13 +25,25 @@
 
 supported_content = ( "rpms", )
 
+from gi.repository import Modulemd
+
 class ModuleProfile(object):
     """Class representing a particular module profile."""
 
-    def __init__(self):
+    def __init__(self, profile=None, name=None, parent=None):
         """Creates a new ModuleProfile instance."""
-        self.description = ""
-        self.rpms = set()
+        if profile:
+            if isinstance(profile, Modulemd.Profile):
+                self.profile = profile
+            else:
+                raise TypeError("Supplied value is not a profile")
+        else:
+            self.profile = Modulemd.Profile()
+
+        if parent:
+            self.parent = parent
+        if name:
+            self.name = name
 
     def __repr__(self):
         return ("<ModuleProfile: "
@@ -50,18 +62,22 @@ class ModuleProfile(object):
     def description(self):
         """A string property representing a detailed description of the
         profile."""
-        return self._description
+        return self.profile.get_description()
 
     @description.setter
     def description(self, s):
         if not isinstance(s, str):
             raise TypeError("profile.description: data type not supported")
-        self._description = s
+        self.profile.set_description(s)
+        if hasattr(self, 'parent') and hasattr(self, 'name'):
+            profiles = self.parent.module.get_profiles()
+            profiles[self.name] = self.profile
+            self.parent.module.set_profiles(profiles)
 
     @property
     def rpms(self):
         """A set of binary RPM packages included in this profile."""
-        return self._rpms
+        return set(sorted(self.profile.get_rpms().get()))
 
     @rpms.setter
     def rpms(self, ss):
@@ -70,7 +86,15 @@ class ModuleProfile(object):
         for v in ss:
             if not isinstance(v, str):
                 raise TypeError("profile.rpms: data type not supported")
-        self._rpms = ss
+
+        simpleset = Modulemd.SimpleSet()
+        simpleset.set(list(ss))
+        self.profile.set_rpms(simpleset)
+
+        if hasattr(self, 'parent') and hasattr(self, 'name'):
+            profiles = self.parent.module.get_profiles()
+            profiles[self.name] = self.profile
+            self.parent.module.set_profiles(profiles)
 
     def add_rpm(self, s):
         """Adds a binary RPM package to the profile set.
@@ -79,7 +103,14 @@ class ModuleProfile(object):
         """
         if not isinstance(s, str):
             raise TypeError("profile.add_rpm: data type not supported")
-        self._rpms.add(s)
+        simpleset = self.profile.get_rpms()
+        simpleset.add(s)
+        self.profile.set_rpms(simpleset)
+
+        if hasattr(self, 'parent') and hasattr(self, 'name'):
+            profiles = self.parent.module.get_profiles()
+            profiles[self.name] = self.profile
+            self.parent.module.set_profiles(profiles)
 
     def del_rpm(self, s):
         """Removes the supplied package name from the profile package set.
@@ -88,8 +119,20 @@ class ModuleProfile(object):
         """
         if not isinstance(s, str):
             raise TypeError("profile.del_rpm: data type not supported")
-        self._rpms.discard(s)
+        simpleset = self.profile.get_rpms()
+        simpleset.remove(s)
+        self.profile.set_rpms(simpleset)
+
+        if hasattr(self, 'parent') and hasattr(self, 'name'):
+            profiles = self.parent.module.get_profiles()
+            profiles[self.name] = self.profile
+            self.parent.module.set_profiles(profiles)
 
     def clear_rpms(self):
         """Clear the profile binary RPM package set."""
-        self._rpms.clear()
+        self.profile.set_rpms(Modulemd.SimpleSet())
+
+        if hasattr(self, 'parent') and hasattr(self, 'name'):
+            profiles = self.parent.module.get_profiles()
+            profiles[self.name] = self.profile
+            self.parent.module.set_profiles(profiles)

From e74ef3cf45622153b5a1e9e156bbcf6b1fa8223b Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 14/48] update required libmodulemd version


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index a2a390c..c014214 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -60,7 +60,7 @@ from modulemd.profile import ModuleProfile
 
 import gi
 from gi.repository import GLib
-gi.require_version('Modulemd', '0.1')
+gi.require_version('Modulemd', '0.2')
 from gi.repository import Modulemd
 
 supported_mdversions = ( 1, )

From b7f27beefb94120ea715724b22f7a9bbf467f030 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 15/48] fix typo


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index c014214..957e015 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -223,7 +223,7 @@ class ModuleMetadata(object):
 
         :param str s: Raw metadata in YAML
         """
-        self.module = Modulemd.Module.new_from_string(f)
+        self.module = Modulemd.Module.new_from_string(s)
 
     def loadd(self, d):
         """Loads metadata from a dictionary.

From e27c54544e00b053f763b6eb18ad26c3de904642 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 16/48] split up monolithic test method


---

diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py
index 8912912..0eff5db 100644
--- a/modulemd/tests/test_io.py
+++ b/modulemd/tests/test_io.py
@@ -35,23 +35,52 @@ sys.path.insert(0, os.path.join(DIR, ".."))
 
 from modulemd import ModuleMetadata, dump_all, load_all
 
-class TestIO(unittest.TestCase):
+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")
+
+
+class TestIO(_TestIOBase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(TestIO, cls).setUpClass()
+        cls.mmd.load("spec.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(),
@@ -63,22 +92,40 @@ class TestIO(unittest.TestCase):
                                    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"]))
@@ -88,10 +135,16 @@ class TestIO(unittest.TestCase):
                 "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,
@@ -114,6 +167,8 @@ class TestIO(unittest.TestCase):
                 "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,
@@ -124,6 +179,8 @@ class TestIO(unittest.TestCase):
                 "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",
@@ -132,6 +189,8 @@ class TestIO(unittest.TestCase):
                      "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")
 

From d973a4af7c15a7e40720e2190b0c1a18668c2d20 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 17/48] import gobject-introspected modules early


This is so we can keep the required module version in only one place.

---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 957e015..5ece126 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -48,6 +48,11 @@ import yaml
 if sys.version_info > (3,):
     long = int
 
+import gi
+from gi.repository import GLib
+gi.require_version('Modulemd', '0.2')
+from gi.repository import Modulemd
+
 from modulemd.api import ModuleAPI
 from modulemd.artifacts import ModuleArtifacts
 from modulemd.buildopts import ModuleBuildopts
@@ -58,11 +63,6 @@ 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, )
 
 def load_all(f):

From 22b08d0f4bf4d1250d5014665e435912d300ae27 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 18/48] make pyflakes complain less


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 5ece126..f01e89f 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -643,33 +643,33 @@ class ModuleMetadata(object):
             elif type(item) == dict:
                 l_variant.append(ModuleMetadata._variant_dict(item))
             else:
-                raise TypeError ("Cannot convert unknown type")
-        return GLib.Variant('av', l_variant);
+                raise TypeError("Cannot convert unknown type")
+        return GLib.Variant('av', l_variant)
 
     def _variant_dict_values(d):
         if not isinstance(d, dict):
-            raise TypeError ("Only dictionaries are supported for mappings")
+            raise TypeError("Only dictionaries are supported for mappings")
 
         d_variant = dict()
         for k, v in d.items():
             if type(v) == str:
-                d_variant[k] = ModuleMetadata._variant_str(v);
+                d_variant[k] = ModuleMetadata._variant_str(v)
                 pass
             elif type(v) == list:
-                d_variant[k] = ModuleMetadata._variant_list(v);
+                d_variant[k] = ModuleMetadata._variant_list(v)
             elif type(v) == dict:
-                d_variant[k] = ModuleMetadata._variant_dict(v);
+                d_variant[k] = ModuleMetadata._variant_dict(v)
             else:
-                raise TypeError ("Cannot convert unknown type")
+                raise TypeError("Cannot convert unknown type")
         return d_variant
 
     def _variant_dict(d):
         if not isinstance(d, dict):
-            raise TypeError ("Only dictionaries are supported for mappings")
+            raise TypeError("Only dictionaries are supported for mappings")
 
         d_variant = ModuleMetadata._variant_dict_values(d)
 
-        return GLib.Variant('a{sv}', d_variant);
+        return GLib.Variant('a{sv}', d_variant)
 
 
     @xmd.setter
@@ -686,7 +686,7 @@ class ModuleMetadata(object):
         """A dictionary property representing the module profiles."""
         # TODO: return the profiles as a dict of ModuleProfile types
         d = dict()
-        for k,v in self.module.get_profiles().items():
+        for k, v in self.module.get_profiles().items():
             d[k] = ModuleProfile(profile=v, parent=self, name=k)
 
         return d
@@ -701,7 +701,7 @@ class ModuleMetadata(object):
 
         profiles = self.module.get_profiles()
 
-        for k,v in d.items():
+        for k, v in d.items():
             profile = Modulemd.Profile()
             if v.description:
                 profile.set_description(v.description)

From 407c100b51db0442293fa79d0dd74a5fc49853cb Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 19/48] don't overwrite self._buildopts on each access


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index f01e89f..c40c21e 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -752,7 +752,8 @@ class ModuleMetadata(object):
         """A ModuleBuildopts instance representing the additional
         module components build options.
         """
-        self._buildopts = ModuleBuildopts(parent=self)
+        if not getattr(self, '_buildopts', None):
+            self.buildopts = ModuleBuildopts(parent=self)
         return self._buildopts
 
     @buildopts.setter

From 04e2d2f5efde8b161cd69b814780a055d1afe77b Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 20/48] convert ModuleComponents etc. to libmodulemd


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index c40c21e..4c8b448 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -767,13 +767,19 @@ class ModuleMetadata(object):
         """A ModuleComponents instance property representing the components
         defining the module.
         """
+        if not hasattr(self, '_components'):
+            self._components = ModuleComponents(parent=self)
+        self._components.rpms = self.module.get_rpm_components()
+        self._components.modules = self.module.get_module_components()
         return self._components
 
     @components.setter
     def components(self, o):
         if not isinstance(o, ModuleComponents):
             raise TypeError("components: data type not supported")
-        self._components = o
+
+        self.module.set_rpm_components(o.rpms)
+        self.module.set_module_components(o.modules)
 
     @property
     def artifacts(self):
diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index 60ee9a6..b7ef95a 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -23,18 +23,22 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+from gi.repository import Modulemd
+
 from modulemd.components.module import ModuleComponentModule
 from modulemd.components.rpm import ModuleComponentRPM
 
+
 supported_content = ( "rpms", "modules", )
 
 class ModuleComponents(object):
     """Class representing components of a module."""
 
-    def __init__(self):
+    def __init__(self, parent=None):
         """Creates a new ModuleComponents instance."""
-        self.modules = dict()
-        self.rpms = dict()
+        self.parent = parent
+        self._rpms = {}
+        self._modules = {}
 
     def __repr__(self):
         return ("<ModuleComponents: "
@@ -57,6 +61,41 @@ class ModuleComponents(object):
         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
@@ -73,6 +112,8 @@ class ModuleComponents(object):
                 raise TypeError("components.rpms: data type not supported")
         self._rpms = d
 
+        self._save_to_libmodulemd()
+
     def add_rpm(self, name, rationale, buildorder=0,
             repository="", ref="", cache="", arches=set(), multilib=set()):
         """Adds an RPM to the set of module components."""
@@ -85,6 +126,8 @@ class ModuleComponents(object):
         component.multilib = multilib
         self._rpms[name] = component
 
+        self._save_to_libmodulemd()
+
     def del_rpm(self, s):
         """Removes the supplied RPM from the set of module components."""
         if not isinstance(s, str):
@@ -92,10 +135,14 @@ class ModuleComponents(object):
         if s in self._rpms:
             del self._rpms[s]
 
+        self._save_to_libmodulemd()
+
     def clear_rpms(self):
         """Clear the RPM component dictionary."""
         self._rpms.clear()
 
+        self._save_to_libmodulemd()
+
     @property
     def modules(self):
         """A dictionary of module-type components in this module.  The keys are
@@ -112,6 +159,8 @@ class ModuleComponents(object):
                 raise TypeError("components.modules: data type not supported")
         self._modules = d
 
+        self._save_to_libmodulemd()
+
     def add_module(self, name, rationale, buildorder=0,
             repository="", ref=""):
         component = ModuleComponentModule(name, rationale)
@@ -120,6 +169,8 @@ class ModuleComponents(object):
         component.ref = ref
         self._modules[name] = component
 
+        self._save_to_libmodulemd()
+
     def del_module(self, s):
         """Removes the supplied module from the set of module components."""
         if not isinstance(s, str):
@@ -127,6 +178,10 @@ class ModuleComponents(object):
         if s in self._modules:
             del self._modules[s]
 
+        self._save_to_libmodulemd()
+
     def clear_modules(self):
         """Clear the module-type component dictionary."""
         self._modules.clear()
+
+        self._save_to_libmodulemd()

From f1d2df43c6c64aa6f58f275e3aee7eded09f16ca Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 21/48] only use libmodulemd functions in __init__()


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 4c8b448..83dadcb 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -159,10 +159,6 @@ class ModuleMetadata(object):
         self.module.set_module_components({})
         self.module.set_rpm_artifacts(Modulemd.SimpleSet())
 
-        self.buildopts = ModuleBuildopts()
-        self.components = ModuleComponents()
-
-
     def __repr__(self):
         return ("<ModuleMetadata: "
                 "mdversion: {0}, "

From 24a47b19e6a43100b330d2a85a1f647037030175 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 22/48] components: cope with native objects in setters


---

diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index b7ef95a..005f3c8 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -108,8 +108,13 @@ class ModuleComponents(object):
         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")
+            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 isinstance(v, Modulemd.ComponentRpm):
+                d[k] = ModuleComponentRPM._new_from_native(k, v)
         self._rpms = d
 
         self._save_to_libmodulemd()
@@ -155,8 +160,13 @@ class ModuleComponents(object):
         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")
+            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 isinstance(v, Modulemd.ComponentModule):
+                d[k] = ModuleComponentModule._new_from_native(k, v)
         self._modules = d
 
         self._save_to_libmodulemd()
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index 9a0d2c2..509d2dd 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -35,6 +35,13 @@ class ModuleComponentModule(ModuleComponentBase):
         self.repository = repository
         self.ref = ref
 
+    @classmethod
+    def _new_from_native(cls, name, native_comp):
+        return cls(name, native_comp.get_rationale(),
+                   buildorder=native_comp.get_buildorder() or 0,
+                   repository=native_comp.get_repository() or "",
+                   ref=native_comp.get_ref() or "")
+
     def __repr__(self):
         return ("<ModuleComponentModule: "
                 "name: {0}, "
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index 3d24430..b703af2 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -38,6 +38,16 @@ class ModuleComponentRPM(ModuleComponentBase):
         self.arches = arches
         self.multilib = multilib
 
+    @classmethod
+    def _new_from_native(cls, name, native_comp):
+        return cls(name, 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()))
+
     def __repr__(self):
         return ("<ModuleComponentRPM: "
                 "name: {0}, "

From d424a1d1e70b01f31ee852986898b575a24efbb8 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 23/48] implement ModuleComponent(RPM|Module).__eq__()


This allows comparing objects with different identity for equality. This
is needed because these objects usually have quite a short lifetime
because they're frequently recreated from libmodulemd data.

---

diff --git a/modulemd/components/base.py b/modulemd/components/base.py
index 0fd97e0..07ec0cc 100644
--- a/modulemd/components/base.py
+++ b/modulemd/components/base.py
@@ -52,6 +52,14 @@ class ModuleComponentBase(object):
 
     __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."""
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index 509d2dd..4aaaf58 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -62,6 +62,13 @@ class ModuleComponentModule(ModuleComponentBase):
 
     __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
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index b703af2..3e8c0e8 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -75,6 +75,16 @@ class ModuleComponentRPM(ModuleComponentBase):
 
     __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

From 30e2d91b45352c6ea49a73dc48928ba450a35739 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 24/48] reformat license and copyright headers


---

diff --git a/LICENSE b/LICENSE
index 0e7dda2..73c5ddb 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,9 +1,21 @@
 The MIT License (MIT)
 
-Copyright (c) 2016  Red Hat, Inc.
+Copyright © 2016 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.
diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 83dadcb..682ba59 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/api.py b/modulemd/api.py
index a48d848..e4862ab 100644
--- a/modulemd/api.py
+++ b/modulemd/api.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/artifacts.py b/modulemd/artifacts.py
index efdc228..88c20da 100644
--- a/modulemd/artifacts.py
+++ b/modulemd/artifacts.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016, 2017  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
@@ -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,
diff --git a/modulemd/buildopts/__init__.py b/modulemd/buildopts/__init__.py
index 9f4cc11..ceac015 100644
--- a/modulemd/buildopts/__init__.py
+++ b/modulemd/buildopts/__init__.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016, 2017  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
@@ -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,
diff --git a/modulemd/buildopts/base.py b/modulemd/buildopts/base.py
index e3000a8..b1d3fc0 100644
--- a/modulemd/buildopts/base.py
+++ b/modulemd/buildopts/base.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016, 2017  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
@@ -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,
diff --git a/modulemd/buildopts/rpms.py b/modulemd/buildopts/rpms.py
index 6dc0640..4681ab3 100644
--- a/modulemd/buildopts/rpms.py
+++ b/modulemd/buildopts/rpms.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016, 2017  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
@@ -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,
diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index 005f3c8..af1014c 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/components/base.py b/modulemd/components/base.py
index 07ec0cc..c00dd09 100644
--- a/modulemd/components/base.py
+++ b/modulemd/components/base.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index 4aaaf58..db81e95 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index 3e8c0e8..63882e3 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/filter.py b/modulemd/filter.py
index b2cecd1..d4b91fc 100644
--- a/modulemd/filter.py
+++ b/modulemd/filter.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/profile.py b/modulemd/profile.py
index 9e2d2ad..4ddc5f9 100644
--- a/modulemd/profile.py
+++ b/modulemd/profile.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/tests/test_basic.py b/modulemd/tests/test_basic.py
index 71bc3d7..8875357 100644
--- a/modulemd/tests/test_basic.py
+++ b/modulemd/tests/test_basic.py
@@ -1,8 +1,7 @@
 #/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/tests/test_convenience.py b/modulemd/tests/test_convenience.py
index 4c40617..dbdaf55 100644
--- a/modulemd/tests/test_convenience.py
+++ b/modulemd/tests/test_convenience.py
@@ -1,8 +1,7 @@
 #/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py
index 0eff5db..8bddd9f 100644
--- a/modulemd/tests/test_io.py
+++ b/modulemd/tests/test_io.py
@@ -1,8 +1,7 @@
 #/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,
diff --git a/modulemd/tests/test_validation.py b/modulemd/tests/test_validation.py
index 563dd2a..4b54a61 100644
--- a/modulemd/tests/test_validation.py
+++ b/modulemd/tests/test_validation.py
@@ -1,8 +1,7 @@
 #/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-
-# Copyright (c) 2016  Red Hat, Inc.
+# Copyright © 2016 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,

From 940d0fd53145000bf91a5fafd2aa1455aa717fa2 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 25/48] add TreeObj and RPMsMixin classes


These serve to share code between objects that represent non-leaf nodes
of a modulemd tree and those that contain a set of RPM names, especially
with regards to dispatching things to the native side, i.e. libmodulemd.

---

diff --git a/modulemd/legacy_wrappers.py b/modulemd/legacy_wrappers.py
new file mode 100644
index 0000000..a12d057
--- /dev/null
+++ b/modulemd/legacy_wrappers.py
@@ -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()

From 498097857c92571ad12de033d4d0674f4d18e620 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 26/48] use relative imports within modulemd package


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 682ba59..5deadc5 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -52,15 +52,15 @@ from gi.repository import GLib
 gi.require_version('Modulemd', '0.2')
 from gi.repository import Modulemd
 
-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
+from .api import ModuleAPI
+from .artifacts import ModuleArtifacts
+from .buildopts import ModuleBuildopts
+from .buildopts.rpms import ModuleBuildoptsRPMs
+from .components import ModuleComponents
+from .components.module import ModuleComponentModule
+from .components.rpm import ModuleComponentRPM
+from .filter import ModuleFilter
+from .profile import ModuleProfile
 
 supported_mdversions = ( 1, )
 
diff --git a/modulemd/buildopts/__init__.py b/modulemd/buildopts/__init__.py
index ceac015..21691f6 100644
--- a/modulemd/buildopts/__init__.py
+++ b/modulemd/buildopts/__init__.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-from modulemd.buildopts.rpms import ModuleBuildoptsRPMs
+from .rpms import ModuleBuildoptsRPMs
 
 supported_content = ( "rpms", )
 
diff --git a/modulemd/buildopts/rpms.py b/modulemd/buildopts/rpms.py
index 4681ab3..a552e0c 100644
--- a/modulemd/buildopts/rpms.py
+++ b/modulemd/buildopts/rpms.py
@@ -22,7 +22,7 @@
 #
 # 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."""
diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index af1014c..44bf4ab 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -24,8 +24,8 @@
 
 from gi.repository import Modulemd
 
-from modulemd.components.module import ModuleComponentModule
-from modulemd.components.rpm import ModuleComponentRPM
+from .module import ModuleComponentModule
+from .rpm import ModuleComponentRPM
 
 
 supported_content = ( "rpms", "modules", )
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index db81e95..efa9cfb 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-from modulemd.components.base import ModuleComponentBase
+from .base import ModuleComponentBase
 
 class ModuleComponentModule(ModuleComponentBase):
     """A component class for handling module-type content."""
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index 63882e3..96d305f 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-from modulemd.components.base import ModuleComponentBase
+from .base import ModuleComponentBase
 
 class ModuleComponentRPM(ModuleComponentBase):
     """A component class for handling RPM content."""

From 481460bae470d13527fd3e33fd57718b4c2b2ac4 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 27/48] don't fake `long` type in Python 3


Instead, do it like six without actually pulling it in.

---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 5deadc5..89cacf3 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -44,9 +44,6 @@ import datetime
 import dateutil.parser
 import yaml
 
-if sys.version_info > (3,):
-    long = int
-
 import gi
 from gi.repository import GLib
 gi.require_version('Modulemd', '0.2')
@@ -62,6 +59,12 @@ from .components.rpm import ModuleComponentRPM
 from .filter import ModuleFilter
 from .profile import ModuleProfile
 
+
+if sys.version_info > (3,):
+    integer_types = (int,)
+else:
+    integer_types = (int, long)  # noqa
+
 supported_mdversions = ( 1, )
 
 def load_all(f):
@@ -263,7 +266,7 @@ class ModuleMetadata(object):
 
     @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")
@@ -298,7 +301,7 @@ class ModuleMetadata(object):
 
     @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")
diff --git a/modulemd/components/base.py b/modulemd/components/base.py
index c00dd09..adedb65 100644
--- a/modulemd/components/base.py
+++ b/modulemd/components/base.py
@@ -25,7 +25,10 @@
 import sys
 
 if sys.version_info > (3,):
-    long = int
+    integer_types = (int,)
+else:
+    integer_types = (int, long)
+
 
 class ModuleComponentBase(object):
     """A base class for definining module component types."""
@@ -92,6 +95,6 @@ class ModuleComponentBase(object):
 
     @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

From e294cff2872e874f06a3160a3879e6e4c11dedf0 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 28/48] pep8: remove unused imports, reformat some things


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 89cacf3..6136167 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -41,7 +41,6 @@ Example usage:
 
 import sys
 import datetime
-import dateutil.parser
 import yaml
 
 import gi
@@ -52,10 +51,7 @@ from gi.repository import Modulemd
 from .api import ModuleAPI
 from .artifacts import ModuleArtifacts
 from .buildopts import ModuleBuildopts
-from .buildopts.rpms import ModuleBuildoptsRPMs
 from .components import ModuleComponents
-from .components.module import ModuleComponentModule
-from .components.rpm import ModuleComponentRPM
 from .filter import ModuleFilter
 from .profile import ModuleProfile
 
@@ -65,7 +61,8 @@ if sys.version_info > (3,):
 else:
     integer_types = (int, long)  # noqa
 
-supported_mdversions = ( 1, )
+supported_mdversions = (1,)
+
 
 def load_all(f):
     """Loads a metadata file containing multiple modulemd documents
@@ -83,6 +80,7 @@ def load_all(f):
 
     return l
 
+
 def loads_all(s):
     """Loads multiple modulemd documents from a YAML multidocument
     string.
@@ -99,6 +97,7 @@ def loads_all(s):
 
     return l
 
+
 def dump_all(f, l):
     """Dumps a list of ModuleMetadata instances into a file.
 
@@ -111,6 +110,7 @@ def dump_all(f, l):
 
     return Modulemd.Module.dump_all(mmds, f)
 
+
 def dumps_all(l):
     """Dumps a list of ModuleMetadata instance into a YAML multidocument
     string.
@@ -123,6 +123,7 @@ def dumps_all(l):
 
     return Modulemd.Module.dumps_all(mmds)
 
+
 class ModuleMetadata(object):
     """Class representing the whole module."""
 
@@ -162,52 +163,20 @@ class ModuleMetadata(object):
         self.module.set_rpm_artifacts(Modulemd.SimpleSet())
 
     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)
-                )
+        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 load(self, f):
         """Loads a metadata file into the instance.
@@ -669,7 +638,6 @@ class ModuleMetadata(object):
 
         return GLib.Variant('a{sv}', d_variant)
 
-
     @xmd.setter
     def xmd(self, d):
         if not isinstance(d, dict):
diff --git a/modulemd/api.py b/modulemd/api.py
index e4862ab..3821daf 100644
--- a/modulemd/api.py
+++ b/modulemd/api.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-supported_content = ( "rpms", )
+supported_content = ("rpms",)
 
 from gi.repository import Modulemd
 
@@ -35,10 +35,7 @@ class ModuleAPI(object):
         self.parent = parent
 
     def __repr__(self):
-        return ("<ModuleAPI: "
-                "rpms: {0}>").format(
-                        repr(sorted(self.rpms))
-                        )
+        return "<ModuleAPI: rpms: {!r}>".format(sorted(self.rpms))
 
     def __bool__(self):
         return True if self.rpms else False
diff --git a/modulemd/artifacts.py b/modulemd/artifacts.py
index 88c20da..b42d75d 100644
--- a/modulemd/artifacts.py
+++ b/modulemd/artifacts.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-supported_content = ( "rpms", )
+supported_content = ("rpms",)
 
 from gi.repository import Modulemd
 
@@ -35,10 +35,7 @@ class ModuleArtifacts(object):
         self.parent = parent
 
     def __repr__(self):
-        return ("<ModuleArtifacts: "
-                "rpms: {0}>").format(
-                        repr(sorted(self.rpms))
-                        )
+        return "<ModuleArtifacts: rpms: {!r}>".format(sorted(self.rpms))
 
     def __bool__(self):
         return True if self.rpms else False
diff --git a/modulemd/buildopts/__init__.py b/modulemd/buildopts/__init__.py
index 21691f6..b92c9fd 100644
--- a/modulemd/buildopts/__init__.py
+++ b/modulemd/buildopts/__init__.py
@@ -24,7 +24,7 @@
 
 from .rpms import ModuleBuildoptsRPMs
 
-supported_content = ( "rpms", )
+supported_content = ("rpms",)
 
 class ModuleBuildopts(object):
     """Class representing component build options."""
@@ -36,10 +36,7 @@ class ModuleBuildopts(object):
 
 
     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
diff --git a/modulemd/buildopts/base.py b/modulemd/buildopts/base.py
index b1d3fc0..870e6c9 100644
--- a/modulemd/buildopts/base.py
+++ b/modulemd/buildopts/base.py
@@ -22,7 +22,6 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-import sys
 
 class ModuleBuildoptsBase(object):
     """A base class for definining component build options."""
diff --git a/modulemd/buildopts/rpms.py b/modulemd/buildopts/rpms.py
index a552e0c..d601d1e 100644
--- a/modulemd/buildopts/rpms.py
+++ b/modulemd/buildopts/rpms.py
@@ -24,6 +24,7 @@
 
 from .base import ModuleBuildoptsBase
 
+
 class ModuleBuildoptsRPMs(ModuleBuildoptsBase):
     """A buildopts class for handling RPM content build options."""
 
@@ -49,10 +50,7 @@ class ModuleBuildoptsRPMs(ModuleBuildoptsBase):
 
 
     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
diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index 44bf4ab..027c73e 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -28,7 +28,8 @@ from .module import ModuleComponentModule
 from .rpm import ModuleComponentRPM
 
 
-supported_content = ( "rpms", "modules", )
+supported_content = ("rpms", "modules",)
+
 
 class ModuleComponents(object):
     """Class representing components of a module."""
@@ -40,12 +41,8 @@ class ModuleComponents(object):
         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
diff --git a/modulemd/components/base.py b/modulemd/components/base.py
index adedb65..96da94d 100644
--- a/modulemd/components/base.py
+++ b/modulemd/components/base.py
@@ -40,14 +40,9 @@ class ModuleComponentBase(object):
         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
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index efa9cfb..31dbfc7 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -24,11 +24,12 @@
 
 from .base import ModuleComponentBase
 
+
 class ModuleComponentModule(ModuleComponentBase):
     """A component class for handling module-type content."""
 
     def __init__(self, name, rationale, buildorder=0,
-            repository="", ref=""):
+                 repository="", ref=""):
         """Creates a new ModuleComponentModule instance."""
         super(ModuleComponentModule, self).__init__(name, rationale, buildorder)
         self.repository = repository
@@ -42,18 +43,10 @@ class ModuleComponentModule(ModuleComponentBase):
                    ref=native_comp.get_ref() or "")
 
     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
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index 96d305f..9a6e47b 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -24,11 +24,13 @@
 
 from .base import ModuleComponentBase
 
+
 class ModuleComponentRPM(ModuleComponentBase):
     """A component class for handling RPM content."""
 
     def __init__(self, name, rationale, buildorder=0,
-            repository="", ref="", cache="", arches=set(), multilib=set()):
+                 repository="", ref="", cache="", arches=set(),
+                 multilib=set()):
         """Creates a new ModuleComponentRPM instance."""
         super(ModuleComponentRPM, self).__init__(name, rationale, buildorder)
         self.repository = repository
@@ -48,24 +50,12 @@ class ModuleComponentRPM(ModuleComponentBase):
                    multilib=set(native_comp.get_multilib().get()))
 
     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
diff --git a/modulemd/filter.py b/modulemd/filter.py
index d4b91fc..630d9c3 100644
--- a/modulemd/filter.py
+++ b/modulemd/filter.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-supported_content = ( "rpms", )
+supported_content = ("rpms",)
 
 from gi.repository import Modulemd
 
@@ -35,10 +35,7 @@ class ModuleFilter(object):
         self.parent = parent
 
     def __repr__(self):
-        return ("<ModuleFilter: "
-                "rpms: {0}>").format(
-                        repr(sorted(self.rpms))
-                        )
+        return "<ModuleFilter: rpms: {!r}>".format(sorted(self.rpms))
 
     def __bool__(self):
         return True if self.rpms else False
diff --git a/modulemd/profile.py b/modulemd/profile.py
index 4ddc5f9..abd515a 100644
--- a/modulemd/profile.py
+++ b/modulemd/profile.py
@@ -22,7 +22,7 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-supported_content = ( "rpms", )
+supported_content = ("rpms",)
 
 from gi.repository import Modulemd
 
@@ -45,12 +45,9 @@ class ModuleProfile(object):
             self.name = name
 
     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

From ae82ee361d00711c4c5f27bcd492ba10009831f6 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 29/48] reset tree new-style


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 6136167..d04e5bc 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -134,33 +134,7 @@ class ModuleMetadata(object):
         # Under the hood, we will use the ModulemdModule type
         self.module = Modulemd.Module()
 
-        self.module.set_mdversion(max(supported_mdversions))
-        self.module.set_name("")
-        self.module.set_stream("")
-        self.module.set_version(0)
-        self.module.set_context("")
-        self.module.set_arch("")
-        self.module.set_summary("")
-        self.module.set_description("")
-        # Don't pre-set EOL, since we don't have a valid value yet
-        # The Modulemd.Module() default initializes it to an appropriate invalid
-        # date.
-        self.module.set_servicelevels({})
-        self.module.set_module_licenses(Modulemd.SimpleSet())
-        self.module.set_content_licenses(Modulemd.SimpleSet())
-        self.module.set_buildrequires({})
-        self.module.set_requires({})
-        self.module.set_community("")
-        self.module.set_documentation("")
-        self.module.set_tracker("")
-        self.module.set_xmd({})
-        self.module.set_profiles({})
-        self.module.set_rpm_api(Modulemd.SimpleSet())
-        self.module.set_rpm_filter(Modulemd.SimpleSet())
-        self.module.set_rpm_buildopts({})
-        self.module.set_rpm_components({})
-        self.module.set_module_components({})
-        self.module.set_rpm_artifacts(Modulemd.SimpleSet())
+        self.reset()
 
     def __repr__(self):
         return ("<ModuleMetadata: mdversion: {!r}, name: {!r}, stream: {!r},"
@@ -178,6 +152,35 @@ class ModuleMetadata(object):
                     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 = ""
+        self.version = 0
+        self.context = ""
+        self.arch = ""
+        self.summary = ""
+        self.description = ""
+        # Don't pre-set EOL, since we don't have a valid value yet
+        # The Modulemd.Module() default initializes it to an appropriate invalid
+        # date.
+        #self.eol = None
+        self.servicelevels = {}
+        self.module_licenses = set()
+        self.content_licenses = set()
+        self.buildrequires = {}
+        self.requires = {}
+        self.community = ""
+        self.documentation = ""
+        self.tracker = ""
+        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.
 

From 4abab920260ced5c8c0dc85f0fa148d87864abb6 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 30/48] allow unsetting eol date


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index d04e5bc..41f8425 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -161,10 +161,7 @@ class ModuleMetadata(object):
         self.arch = ""
         self.summary = ""
         self.description = ""
-        # Don't pre-set EOL, since we don't have a valid value yet
-        # The Modulemd.Module() default initializes it to an appropriate invalid
-        # date.
-        #self.eol = None
+        self.eol = None
         self.servicelevels = {}
         self.module_licenses = set()
         self.content_licenses = set()
@@ -344,7 +341,10 @@ class ModuleMetadata(object):
         if not isinstance(o, datetime.date) and o is not None:
             raise TypeError("eol: data type not supported")
 
-        d = GLib.Date.new_dmy(o.day, o.month, o.year)
+        if o:
+            d = GLib.Date.new_dmy(o.day, o.month, o.year)
+        else:
+            d = None
         self.module.set_eol(d)
 
     @property

From c83491ea0c9004165962819c4acf115bd0d24f39 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 31/48] fix servicelevels property setter


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 41f8425..b723baf 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -355,7 +355,9 @@ class ModuleMetadata(object):
     @servicelevels.setter
     def servicelevels(self, d):
         if not isinstance(d, dict):
-            self.module.set_servicelevels(d)
+            raise TypeError("servicelevels: data type not supported")
+
+        self.module.set_servicelevels(d)
 
     @property
     def module_licenses(self):

From 7dfca79f57c7473df8e1047f99fcc9dbaf708e5a Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 32/48] make _variant_*() methods real class/static methods


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index b723baf..48d1ee9 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -599,13 +599,15 @@ class ModuleMetadata(object):
             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)
 
-    def _variant_list(l):
+    @classmethod
+    def _variant_list(cls, l):
         l_variant = list()
         for item in l:
             if type(item) == str:
@@ -618,28 +620,30 @@ class ModuleMetadata(object):
                 raise TypeError("Cannot convert unknown type")
         return GLib.Variant('av', l_variant)
 
-    def _variant_dict_values(d):
+    @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] = ModuleMetadata._variant_str(v)
+                d_variant[k] = cls._variant_str(v)
                 pass
             elif type(v) == list:
-                d_variant[k] = ModuleMetadata._variant_list(v)
+                d_variant[k] = cls._variant_list(v)
             elif type(v) == dict:
-                d_variant[k] = ModuleMetadata._variant_dict(v)
+                d_variant[k] = cls._variant_dict(v)
             else:
                 raise TypeError("Cannot convert unknown type")
         return d_variant
 
-    def _variant_dict(d):
+    @classmethod
+    def _variant_dict(cls, d):
         if not isinstance(d, dict):
             raise TypeError("Only dictionaries are supported for mappings")
 
-        d_variant = ModuleMetadata._variant_dict_values(d)
+        d_variant = cls._variant_dict_values(d)
 
         return GLib.Variant('a{sv}', d_variant)
 
@@ -648,7 +652,7 @@ class ModuleMetadata(object):
         if not isinstance(d, dict):
             raise TypeError("xmd: data type not supported")
 
-        xmd = ModuleMetadata._variant_dict_values(d)
+        xmd = self._variant_dict_values(d)
 
         self.module.set_xmd(xmd)
 

From 58d8132a4221506786b0aaddb44288598aa7b608 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 33/48] migrate ModuleAPI to TreeObj/RPMsMixin


---

diff --git a/modulemd/api.py b/modulemd/api.py
index 3821daf..29ce080 100644
--- a/modulemd/api.py
+++ b/modulemd/api.py
@@ -22,78 +22,22 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
-supported_content = ("rpms",)
+from .legacy_wrappers import TreeObj, RPMsMixin
 
-from gi.repository import Modulemd
 
-class ModuleAPI(object):
+supported_content = ('rpms',)
+
+
+class ModuleAPI(TreeObj, RPMsMixin):
     """Class representing a particular module API."""
 
-    def __init__(self, parent=None):
-        """Creates a new ModuleAPI instance."""
-        self.rpms = set()
-        self.parent = parent
+    tree_identity = 'api'
 
     def __repr__(self):
         return "<ModuleAPI: rpms: {!r}>".format(sorted(self.rpms))
 
-    def __bool__(self):
-        return True if self.rpms else False
-
-    __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
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_api(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("api.add_rpm: data type not supported")
-        self._rpms.add(s)
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_api(ss)
-
-    def del_rpm(self, s):
-        """Removes the supplied package name from the API package set.
-
-        :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)
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_api(ss)
-
-    def clear_rpms(self):
-        """Clear the API binary RPM package set."""
-        self._rpms.clear()
+    def get_rpm_set(self):
+        return self.parent.module.get_rpm_api()
 
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_api(ss)
+    def set_rpm_set(self, ss):
+        self.parent.module.set_rpm_api(ss)

From 0dd8f1715dc20723e76d4b72d05f14b480c7ba13 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 34/48] migrate ModuleArtifacts to TreeObj/RPMsMixin


---

diff --git a/modulemd/artifacts.py b/modulemd/artifacts.py
index b42d75d..e575f2f 100644
--- a/modulemd/artifacts.py
+++ b/modulemd/artifacts.py
@@ -22,78 +22,28 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+from .legacy_wrappers import TreeObj, RPMsMixin
+
+
 supported_content = ("rpms",)
 
-from gi.repository import Modulemd
 
-class ModuleArtifacts(object):
+class ModuleArtifacts(TreeObj, RPMsMixin):
     """Class representing a particular module artifacts."""
 
-    def __init__(self, parent=None):
+    tree_identity = 'artifacts'
+
+    def __init__(self, rpms=None, parent=None):
         """Creates a new ModuleArtifacts instance."""
-        self.rpms = set()
-        self.parent = parent
+        super(ModuleArtifacts, self).__init__(parent=parent)
+        if rpms:
+            self.rpms = rpms
 
     def __repr__(self):
         return "<ModuleArtifacts: rpms: {!r}>".format(sorted(self.rpms))
 
-    def __bool__(self):
-        return True if self.rpms else False
-
-    __nonzero__ = __bool__
-
-    @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
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_artifacts(ss)
-
-    def add_rpm(self, s):
-        """Adds an NEVRA to the artifact set.
-
-        :param str s: RPM NEVRA
-        """
-        if not isinstance(s, str):
-            raise TypeError("artifacts.add_rpm: data type not supported")
-        self._rpms.add(s)
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_artifacts(ss)
-
-    def del_rpm(self, s):
-        """Removes the supplied NEVRA from the artifact set.
-
-        :param str s: RPM NEVRA
-        """
-        if not isinstance(s, str):
-            raise TypeError("artifacts.del_rpm: data type not supported")
-        self._rpms.discard(s)
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_artifacts(ss)
-
-    def clear_rpms(self):
-        """Clear the RPM artifacts set."""
-        self._rpms.clear()
+    def get_rpm_set(self):
+        return self.parent.module.get_rpm_artifacts()
 
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_artifacts(ss)
+    def set_rpm_set(self, ss):
+        self.parent.module.set_rpm_artifacts(ss)

From d87fe7325f1095452d5129c312e4cb50fad22655 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 35/48] migrate ModuleFilter to TreeObj/RPMsMixin


---

diff --git a/modulemd/filter.py b/modulemd/filter.py
index 630d9c3..c1e47a7 100644
--- a/modulemd/filter.py
+++ b/modulemd/filter.py
@@ -22,78 +22,22 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+from .legacy_wrappers import TreeObj, RPMsMixin
+
+
 supported_content = ("rpms",)
 
-from gi.repository import Modulemd
 
-class ModuleFilter(object):
+class ModuleFilter(TreeObj, RPMsMixin):
     """Class representing a particular module filter."""
 
-    def __init__(self, parent=None):
-        """Creates a new ModuleFilter instance."""
-        self.rpms = set()
-        self.parent = parent
+    tree_identity = 'filter'
 
     def __repr__(self):
         return "<ModuleFilter: rpms: {!r}>".format(sorted(self.rpms))
 
-    def __bool__(self):
-        return True if self.rpms else False
-
-    __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
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_filter(ss)
-
-    def add_rpm(self, s):
-        """Adds a binary RPM package to the filter set.
-
-        :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)
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_filter(ss)
-
-    def del_rpm(self, s):
-        """Removes the supplied package name from the filter package set.
-
-        :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)
-
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_filter(ss)
-
-    def clear_rpms(self):
-        """Clear the filter binary RPM package set."""
-        self._rpms.clear()
+    def get_rpm_set(self):
+        return self.parent.module.get_rpm_filter()
 
-        if hasattr(self, 'parent'):
-            ss = Modulemd.SimpleSet()
-            ss.set(list(self.rpms))
-            self.parent.module.set_rpm_filter(ss)
+    def set_rpm_set(self, ss):
+        self.parent.module.set_rpm_filter(ss)

From c4570c271d54228f709a8ef65871a7481642d48f Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 36/48] migrate ModuleBuildopts* to TreeObj


---

diff --git a/modulemd/buildopts/__init__.py b/modulemd/buildopts/__init__.py
index b92c9fd..9e7e06f 100644
--- a/modulemd/buildopts/__init__.py
+++ b/modulemd/buildopts/__init__.py
@@ -22,18 +22,24 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+from ..legacy_wrappers import TreeObj
+
 from .rpms import ModuleBuildoptsRPMs
 
-supported_content = ("rpms",)
 
-class ModuleBuildopts(object):
+supported_content = ('rpms',)
+
+
+class ModuleBuildopts(TreeObj):
     """Class representing component build options."""
 
-    def __init__(self, parent=None):
-        """Creates a new ModuleBuildopts instance."""
-        self.rpms = ModuleBuildoptsRPMs(parent=parent)
-        self.parent = parent
+    tree_props = ('rpms',)
+    tree_identity = 'buildopts'
 
+    def __init__(self, rpms=None, parent=None):
+        """Creates a new ModuleBuildopts instance."""
+        super(ModuleBuildopts, self).__init__(parent=parent)
+        self._rpms = ModuleBuildoptsRPMs(parent=parent)
 
     def __repr__(self):
         return "<ModuleBuildopts: rpms: {!r}>".format(self.rpms)
@@ -54,9 +60,8 @@ class ModuleBuildopts(object):
     def rpms(self, o):
         if not isinstance(o, ModuleBuildoptsRPMs):
             raise TypeError("buildopts.rpms: data type not supported")
-        self._rpms = o
 
-        if hasattr(self, 'parent') and self.parent is not None:
-            opts = dict()
-            opts['macros'] = o.macros
-            self.parent.set_buildopts(opts)
+        if self.parent:
+            self._rpms.replace(o)
+        else:
+            self._rpms = o
diff --git a/modulemd/buildopts/base.py b/modulemd/buildopts/base.py
index 870e6c9..f82ac7e 100644
--- a/modulemd/buildopts/base.py
+++ b/modulemd/buildopts/base.py
@@ -22,12 +22,13 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+from ..legacy_wrappers import TreeObj
 
-class ModuleBuildoptsBase(object):
-    """A base class for definining component build options."""
 
-    def __init__(self):
-        """Creates a new ModuleBuildoptsBase instance."""
+class ModuleBuildoptsBase(TreeObj):
+    """A base class for defining component build options.
+
+    Nobody knows why it exists."""
 
     def __repr__(self):
         return ("<ModuleBuildoptsBase>")
diff --git a/modulemd/buildopts/rpms.py b/modulemd/buildopts/rpms.py
index d601d1e..2ca97f7 100644
--- a/modulemd/buildopts/rpms.py
+++ b/modulemd/buildopts/rpms.py
@@ -30,24 +30,9 @@ class ModuleBuildoptsRPMs(ModuleBuildoptsBase):
 
     def __init__(self, macros="", parent=None):
         """Creates a new ModuleBuildoptsRPMs instance."""
-        super(ModuleBuildoptsRPMs, self).__init__()
-        self.macros = macros
-        self.parent = parent
-
-        # If macros were specified at initialization, update the
-        # module with it
-        if self.macros:
-            if hasattr(self, 'parent'):
-                opts = dict()
-                opts['macros'] = self.macros
-                self.parent.module.set_rpm_buildopts(opts)
-            return
-
-        if parent:
-            m = self.parent.module.get_rpm_buildopts()
-            if 'macros' in m:
-                self.macros = m['macros']
+        super(ModuleBuildoptsRPMs, self).__init__(parent=parent)
 
+        self.macros = macros
 
     def __repr__(self):
         return "<ModuleBuildoptsRPMs: macros: {!r}>".format(self.macros)
@@ -62,15 +47,23 @@ class ModuleBuildoptsRPMs(ModuleBuildoptsBase):
         """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 hasattr(self, 'parent') and self.parent is not None:
+        if self.parent:
             opts = dict()
-            opts['macros'] = self.macros
+            opts['macros'] = s
             self.parent.module.set_rpm_buildopts(opts)
+        else:
+            self._macros = s

From af315ad0b41fe297aa98e2918e9e7aae9048cf2e Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 18 2018 16:36:31 +0000
Subject: [PATCH 37/48] migrate ModuleComponent* to TreeObj


---

diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index 027c73e..52c812a 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -24,6 +24,8 @@
 
 from gi.repository import Modulemd
 
+from ..legacy_wrappers import TreeObj
+
 from .module import ModuleComponentModule
 from .rpm import ModuleComponentRPM
 
@@ -31,14 +33,17 @@ from .rpm import ModuleComponentRPM
 supported_content = ("rpms", "modules",)
 
 
-class ModuleComponents(object):
+class ModuleComponents(TreeObj):
     """Class representing components of a module."""
 
+    tree_props = ('rpms', 'modules')
+    tree_identity = 'components'
+
     def __init__(self, parent=None):
         """Creates a new ModuleComponents instance."""
-        self.parent = parent
-        self._rpms = {}
-        self._modules = {}
+        super(ModuleComponents, self).__init__(parent=parent)
+        self.rpms = {}
+        self.modules = {}
 
     def __repr__(self):
         return "<ModuleComponents: modules: {!r}, rpms: {!r}>".format(
@@ -97,7 +102,11 @@ class ModuleComponents(object):
         """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):
@@ -109,47 +118,62 @@ class ModuleComponents(object):
                                        Modulemd.ComponentRpm))):
                 raise TypeError("components.rpms: data type not supported"
                                 " ({!r}: {!r})".format(k, v))
-            if isinstance(v, Modulemd.ComponentRpm):
-                d[k] = ModuleComponentRPM._new_from_native(k, v)
-        self._rpms = d
+            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()
 
-        self._save_to_libmodulemd()
+        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
-
-        self._save_to_libmodulemd()
+        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]
-
-        self._save_to_libmodulemd()
+        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._save_to_libmodulemd()
+        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):
@@ -161,33 +185,43 @@ class ModuleComponents(object):
                                        Modulemd.ComponentModule))):
                 raise TypeError("components.modules: data type not supported"
                                 " ({!r}: {!r})".format(k, v))
-            if isinstance(v, Modulemd.ComponentModule):
+            if not self.parent and isinstance(v, Modulemd.ComponentModule):
                 d[k] = ModuleComponentModule._new_from_native(k, v)
-        self._modules = d
+            elif self.parent and isinstance(v, ModuleComponentModule):
+                d[k] = v._to_native()
 
-        self._save_to_libmodulemd()
+        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)
 
-        self._save_to_libmodulemd()
+        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]
-
-        self._save_to_libmodulemd()
+        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._save_to_libmodulemd()
+        self.modules = {}
diff --git a/modulemd/components/base.py b/modulemd/components/base.py
index 96da94d..bf02033 100644
--- a/modulemd/components/base.py
+++ b/modulemd/components/base.py
@@ -24,17 +24,28 @@
 
 import sys
 
+from ..legacy_wrappers import TreeObj
+
+
 if sys.version_info > (3,):
     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
@@ -60,36 +71,56 @@ class ModuleComponentBase(object):
     @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, 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
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index 31dbfc7..3052652 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -22,25 +22,44 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+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, name, native_comp):
-        return cls(name, native_comp.get_rationale(),
+    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 "")
+                   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: {!r}, rationale: {!r},"
@@ -49,8 +68,8 @@ class ModuleComponentModule(ModuleComponentBase):
                     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__
 
@@ -66,23 +85,37 @@ class ModuleComponentModule(ModuleComponentBase):
         """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
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index 9a6e47b..d7f616e 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -22,17 +22,26 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+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()):
+                 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
@@ -40,14 +49,35 @@ class ModuleComponentRPM(ModuleComponentBase):
         self.multilib = multilib
 
     @classmethod
-    def _new_from_native(cls, name, native_comp):
-        return cls(name, native_comp.get_rationale(),
+    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()))
+                   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: {!r}, rationale: {!r},"
@@ -58,7 +88,7 @@ class ModuleComponentRPM(ModuleComponentBase):
                     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
 
@@ -79,39 +109,59 @@ class ModuleComponentRPM(ModuleComponentBase):
         """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):
@@ -119,7 +169,10 @@ class ModuleComponentRPM(ModuleComponentBase):
         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):
@@ -128,7 +181,12 @@ class ModuleComponentRPM(ModuleComponentBase):
         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):
@@ -136,7 +194,10 @@ class ModuleComponentRPM(ModuleComponentBase):
         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):
@@ -145,4 +206,9 @@ class ModuleComponentRPM(ModuleComponentBase):
         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

From 1646442d65eb1a5e412176c2e42cdb91f04d4f4c Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 38/48] migrate ModuleProfile to TreeObj


---

diff --git a/modulemd/profile.py b/modulemd/profile.py
index abd515a..275320e 100644
--- a/modulemd/profile.py
+++ b/modulemd/profile.py
@@ -22,27 +22,39 @@
 #
 # Written by Petr Šabata <contyk@redhat.com>
 
+from gi.repository import Modulemd
+
+from .legacy_wrappers import TreeObj, RPMsMixin
+
+
 supported_content = ("rpms",)
 
-from gi.repository import Modulemd
 
-class ModuleProfile(object):
+class ModuleProfile(TreeObj, RPMsMixin):
     """Class representing a particular module profile."""
 
-    def __init__(self, profile=None, name=None, parent=None):
+    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."""
-        if profile:
-            if isinstance(profile, Modulemd.Profile):
-                self.profile = profile
-            else:
-                raise TypeError("Supplied value is not a profile")
-        else:
+        if parent:
             self.profile = Modulemd.Profile()
+        else:
+            assert not bound
 
-        if parent:
-            self.parent = parent
-        if name:
-            self.name = name
+        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: name: {!r}, description: {!r},"
@@ -50,85 +62,76 @@ class ModuleProfile(object):
                                        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__
 
-    @property
-    def description(self):
-        """A string property representing a detailed description of the
-        profile."""
-        return self.profile.get_description()
+    @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
 
-    @description.setter
-    def description(self, s):
-        if not isinstance(s, str):
-            raise TypeError("profile.description: data type not supported")
-        self.profile.set_description(s)
-        if hasattr(self, 'parent') and hasattr(self, 'name'):
-            profiles = self.parent.module.get_profiles()
-            profiles[self.name] = self.profile
-            self.parent.module.set_profiles(profiles)
-
-    @property
-    def rpms(self):
-        """A set of binary RPM packages included in this profile."""
-        return set(sorted(self.profile.get_rpms().get()))
-
-    @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")
-
-        simpleset = Modulemd.SimpleSet()
-        simpleset.set(list(ss))
-        self.profile.set_rpms(simpleset)
-
-        if hasattr(self, 'parent') and hasattr(self, 'name'):
-            profiles = self.parent.module.get_profiles()
-            profiles[self.name] = self.profile
-            self.parent.module.set_profiles(profiles)
+    def _to_native(self):
+        obj = Modulemd.Profile()
+        obj.set_name(self.name)
+        obj.set_description(self.description)
 
-    def add_rpm(self, s):
-        """Adds a binary RPM package to the profile set.
+        if self.rpms is not None:
+            ss = Modulemd.SimpleSet()
+            ss.set(list(self.rpms))
+            obj.set_rpms(ss)
 
-        :param str s: Binary RPM package name
-        """
-        if not isinstance(s, str):
-            raise TypeError("profile.add_rpm: data type not supported")
-        simpleset = self.profile.get_rpms()
-        simpleset.add(s)
-        self.profile.set_rpms(simpleset)
+        return obj
 
-        if hasattr(self, 'parent') and hasattr(self, 'name'):
+    def _save_to_collection(self):
+        if self.parent and self.bound:
             profiles = self.parent.module.get_profiles()
-            profiles[self.name] = self.profile
+            profiles[self.name] = self._to_native()
             self.parent.module.set_profiles(profiles)
 
-    def del_rpm(self, s):
-        """Removes the supplied package name from the profile package set.
+    def get_rpm_set(self):
+        return self.profile.get_rpms()
 
-        :param str s: Binary RPM package name
-        """
-        if not isinstance(s, str):
-            raise TypeError("profile.del_rpm: data type not supported")
-        simpleset = self.profile.get_rpms()
-        simpleset.remove(s)
-        self.profile.set_rpms(simpleset)
+    def set_rpm_set(self, ss):
+        self.profile.set_rpms(ss)
+        self._save_to_collection()
 
-        if hasattr(self, 'parent') and hasattr(self, 'name'):
-            profiles = self.parent.module.get_profiles()
-            profiles[self.name] = self.profile
-            self.parent.module.set_profiles(profiles)
+    @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
 
-    def clear_rpms(self):
-        """Clear the profile binary RPM package set."""
-        self.profile.set_rpms(Modulemd.SimpleSet())
+    @property
+    def description(self):
+        """A string property representing a detailed description of the
+        profile."""
+        if self.parent:
+            return self.profile.get_description()
+        else:
+            return self._description
 
-        if hasattr(self, 'parent') and hasattr(self, 'name'):
-            profiles = self.parent.module.get_profiles()
-            profiles[self.name] = self.profile
-            self.parent.module.set_profiles(profiles)
+    @description.setter
+    def description(self, s):
+        if s is not None and not isinstance(s, str):
+            raise TypeError("profile.description: data type not supported")
+        if self.parent:
+            self.profile.set_description(s)
+            self._save_to_collection()
+        else:
+            self._description = s

From 5e8113236654f2d1e5242580b80fded704531a44 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 39/48] use TreeObj/RPMsMixin based classes in ModuleMetadata


---

diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 48d1ee9..6ed8119 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -659,12 +659,9 @@ class ModuleMetadata(object):
     @property
     def profiles(self):
         """A dictionary property representing the module profiles."""
-        # TODO: return the profiles as a dict of ModuleProfile types
-        d = dict()
-        for k, v in self.module.get_profiles().items():
-            d[k] = ModuleProfile(profile=v, parent=self, name=k)
-
-        return d
+        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):
@@ -674,101 +671,82 @@ class ModuleMetadata(object):
             if not isinstance(k, str) or not isinstance(v, ModuleProfile):
                 raise TypeError("profiles: data type not supported")
 
-        profiles = self.module.get_profiles()
-
+        # ensure keys and names in profiles agree
         for k, v in d.items():
-            profile = Modulemd.Profile()
-            if v.description:
-                profile.set_description(v.description)
+            v.name = k
 
-            ss = Modulemd.SimpleSet()
-            ss.set(list(v.rpms))
+        self.module.set_profiles({k: v._to_native() for k, v in d.items()})
 
-            profile.set_rpms(ss)
-            profiles[k] = profile
-        self.module.set_profiles(profiles)
+    @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."""
-        api = ModuleAPI(parent=self)
-        api.rpms = set(self.module.get_rpm_api().get())
-        return api
+        return self.tree_objs['api']
 
     @api.setter
     def api(self, o):
         if not isinstance(o, ModuleAPI):
             raise TypeError("api: data type not supported")
 
-        rpms = Modulemd.SimpleSet()
-        rpms.set(list(o.rpms))
-
-        self.module.set_rpm_api(rpms)
+        self.tree_objs['api'].replace(o)
 
     @property
     def filter(self):
         """A ModuleFilter instance representing the module's filter."""
-        filter = ModuleFilter(parent=self)
-        filter.rpms = set(self.module.get_rpm_filter().get())
-        return filter
+        return self.tree_objs['filter']
 
     @filter.setter
     def filter(self, o):
         if not isinstance(o, ModuleFilter):
             raise TypeError("filter: data type not supported")
 
-        rpms = Modulemd.SimpleSet()
-        rpms.set(list(o.rpms))
-
-        self.module.set_rpm_filter(rpms)
+        self.tree_objs['filter'].replace(o)
 
     @property
     def buildopts(self):
         """A ModuleBuildopts instance representing the additional
         module components build options.
         """
-        if not getattr(self, '_buildopts', None):
-            self.buildopts = ModuleBuildopts(parent=self)
-        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.
         """
-        if not hasattr(self, '_components'):
-            self._components = ModuleComponents(parent=self)
-        self._components.rpms = self.module.get_rpm_components()
-        self._components.modules = self.module.get_module_components()
-        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.module.set_rpm_components(o.rpms)
-        self.module.set_module_components(o.modules)
+        self.tree_objs['components'].replace(o)
 
     @property
     def artifacts(self):
         """A ModuleArtifacts instance representing the module's artifacts."""
-        artifacts = ModuleArtifacts(parent=self)
-        artifacts.rpms = set(self.module.get_rpm_artifacts().get())
-        return artifacts
+        return self.tree_objs['artifacts']
 
     @artifacts.setter
     def artifacts(self, o):
         if not isinstance(o, ModuleArtifacts):
             raise TypeError("artifacts: data type not supported")
-
-        rpms = Modulemd.SimpleSet()
-        rpms.set(list(o.rpms))
-
-        self.module.set_rpm_artifacts(rpms)
+        self.tree_objs['artifacts'].replace(o)

From a6ade1450f50bb57a339e8051fa9c7a9bc115f18 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 40/48] fix broken hash-bangs


---

diff --git a/modulemd/tests/test_basic.py b/modulemd/tests/test_basic.py
index 8875357..2c8d728 100644
--- a/modulemd/tests/test_basic.py
+++ b/modulemd/tests/test_basic.py
@@ -1,4 +1,4 @@
-#/usr/bin/python3
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright © 2016 Red Hat, Inc.
diff --git a/modulemd/tests/test_convenience.py b/modulemd/tests/test_convenience.py
index dbdaf55..d2408fe 100644
--- a/modulemd/tests/test_convenience.py
+++ b/modulemd/tests/test_convenience.py
@@ -1,4 +1,4 @@
-#/usr/bin/python3
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright © 2016 Red Hat, Inc.
diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py
index 8bddd9f..f3ebba2 100644
--- a/modulemd/tests/test_io.py
+++ b/modulemd/tests/test_io.py
@@ -1,4 +1,4 @@
-#/usr/bin/python3
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright © 2016 Red Hat, Inc.
diff --git a/modulemd/tests/test_validation.py b/modulemd/tests/test_validation.py
index 4b54a61..c7cac62 100644
--- a/modulemd/tests/test_validation.py
+++ b/modulemd/tests/test_validation.py
@@ -1,4 +1,4 @@
-#/usr/bin/python3
+#!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
 # Copyright © 2016 Red Hat, Inc.

From ceb59ed5ba1e9a1d0c5d3244f4192787188f8b24 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 41/48] update copyright terms


---

diff --git a/LICENSE b/LICENSE
index 73c5ddb..c141775 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright © 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
diff --git a/modulemd/__init__.py b/modulemd/__init__.py
index 6ed8119..9104ea6 100644
--- a/modulemd/__init__.py
+++ b/modulemd/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/api.py b/modulemd/api.py
index 29ce080..8c886da 100644
--- a/modulemd/api.py
+++ b/modulemd/api.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/artifacts.py b/modulemd/artifacts.py
index e575f2f..a0d984e 100644
--- a/modulemd/artifacts.py
+++ b/modulemd/artifacts.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/buildopts/__init__.py b/modulemd/buildopts/__init__.py
index 9e7e06f..8a9a1d5 100644
--- a/modulemd/buildopts/__init__.py
+++ b/modulemd/buildopts/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/buildopts/base.py b/modulemd/buildopts/base.py
index f82ac7e..091099a 100644
--- a/modulemd/buildopts/base.py
+++ b/modulemd/buildopts/base.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/buildopts/rpms.py b/modulemd/buildopts/rpms.py
index 2ca97f7..01f4b91 100644
--- a/modulemd/buildopts/rpms.py
+++ b/modulemd/buildopts/rpms.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/components/__init__.py b/modulemd/components/__init__.py
index 52c812a..614fe59 100644
--- a/modulemd/components/__init__.py
+++ b/modulemd/components/__init__.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/components/base.py b/modulemd/components/base.py
index bf02033..ad3fb59 100644
--- a/modulemd/components/base.py
+++ b/modulemd/components/base.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/components/module.py b/modulemd/components/module.py
index 3052652..c45b418 100644
--- a/modulemd/components/module.py
+++ b/modulemd/components/module.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/components/rpm.py b/modulemd/components/rpm.py
index d7f616e..c07e7cf 100644
--- a/modulemd/components/rpm.py
+++ b/modulemd/components/rpm.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/filter.py b/modulemd/filter.py
index c1e47a7..07a4fc3 100644
--- a/modulemd/filter.py
+++ b/modulemd/filter.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/profile.py b/modulemd/profile.py
index 275320e..c92c684 100644
--- a/modulemd/profile.py
+++ b/modulemd/profile.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/tests/test_basic.py b/modulemd/tests/test_basic.py
index 2c8d728..3a8adca 100644
--- a/modulemd/tests/test_basic.py
+++ b/modulemd/tests/test_basic.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/tests/test_convenience.py b/modulemd/tests/test_convenience.py
index d2408fe..3f01383 100644
--- a/modulemd/tests/test_convenience.py
+++ b/modulemd/tests/test_convenience.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py
index f3ebba2..a87d237 100644
--- a/modulemd/tests/test_io.py
+++ b/modulemd/tests/test_io.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-# Copyright © 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
diff --git a/modulemd/tests/test_validation.py b/modulemd/tests/test_validation.py
index c7cac62..6091db6 100644
--- a/modulemd/tests/test_validation.py
+++ b/modulemd/tests/test_validation.py
@@ -1,7 +1,7 @@
 #!/usr/bin/python3
 # -*- coding: utf-8 -*-
 
-# Copyright © 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

From aac48db935a3f6a615f7c58e035fbc503e221498 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 42/48] move spec.yaml to tests and rename


---

diff --git a/MANIFEST.in b/MANIFEST.in
index a0bb9ba..4d52fe3 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,4 +1,4 @@
 recursive-include docs *
 include README.rst
-include spec.yaml
 include LICENSE
+include modulemd/tests/test.yaml
diff --git a/modulemd/tests/test.yaml b/modulemd/tests/test.yaml
new file mode 100644
index 0000000..6b5b66b
--- /dev/null
+++ b/modulemd/tests/test.yaml
@@ -0,0 +1,282 @@
+# 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
+    # Service levels, optional
+    # This is a dictionary of important dates (and possibly supplementary data
+    # in the future) that describes the end point of certain functionality,
+    # such as the date when the module will transition to "security fixes only"
+    # or go completely end-of-life
+    servicelevels:
+        security:
+            eol: 2019-03-30
+        features:
+            eol: 2018-12-31
+    # 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
diff --git a/spec.yaml b/spec.yaml
deleted file mode 100644
index 6b5b66b..0000000
--- a/spec.yaml
+++ /dev/null
@@ -1,282 +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
-    # Service levels, optional
-    # This is a dictionary of important dates (and possibly supplementary data
-    # in the future) that describes the end point of certain functionality,
-    # such as the date when the module will transition to "security fixes only"
-    # or go completely end-of-life
-    servicelevels:
-        security:
-            eol: 2019-03-30
-        features:
-            eol: 2018-12-31
-    # 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

From e6d9f83793b40e0b8c31abbda2996b35c1c21e66 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 43/48] remove spec comments and add explanation


---

diff --git a/modulemd/tests/test.yaml b/modulemd/tests/test.yaml
index 6b5b66b..1421862 100644
--- a/modulemd/tests/test.yaml
+++ b/modulemd/tests/test.yaml
@@ -1,277 +1,99 @@
-# Document type identifier
+# This file merely contains test data, don't use it as a reference.
 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
-    # Service levels, optional
-    # This is a dictionary of important dates (and possibly supplementary data
-    # in the future) that describes the end point of certain functionality,
-    # such as the date when the module will transition to "security fixes only"
-    # or go completely end-of-life
     servicelevels:
         security:
             eol: 2019-03-30
         features:
             eol: 2018-12-31
-    # 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
diff --git a/modulemd/tests/test_io.py b/modulemd/tests/test_io.py
index a87d237..0ef3ff4 100644
--- a/modulemd/tests/test_io.py
+++ b/modulemd/tests/test_io.py
@@ -34,6 +34,9 @@ sys.path.insert(0, os.path.join(DIR, ".."))
 
 from modulemd import ModuleMetadata, dump_all, load_all
 
+TEST_YAML = os.path.join(DIR, "test.yaml")
+
+
 class _TestIOBase(unittest.TestCase):
     maxDiff = None  # display diff when a test fails
 
@@ -45,7 +48,7 @@ class _TestIOBase(unittest.TestCase):
 class TestIOMMDLoads(_TestIOBase):
 
     def test_load_spec(self):
-        self.mmd.load("spec.yaml")
+        self.mmd.load(TEST_YAML)
 
 
 class TestIO(_TestIOBase):
@@ -53,7 +56,7 @@ class TestIO(_TestIOBase):
     @classmethod
     def setUpClass(cls):
         super(TestIO, cls).setUpClass()
-        cls.mmd.load("spec.yaml")
+        cls.mmd.load(TEST_YAML)
 
     def test_mdversion(self):
         self.assertEqual(self.mmd.mdversion, 1)
@@ -194,7 +197,7 @@ class TestIO(_TestIOBase):
                 "%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()

From 5efcca3643ccd3a24c090ccd8408bce4e904f46c Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:37 +0000
Subject: [PATCH 44/48] remove RPM spec file


---

diff --git a/package/modulemd.spec b/package/modulemd.spec
deleted file mode 100644
index 0d0e7db..0000000
--- a/package/modulemd.spec
+++ /dev/null
@@ -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.

From 767aea4bd4b7ca4df97a3365405ed2e4f31dcb2b Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:38 +0000
Subject: [PATCH 45/48] document that modulemd is a wrapper


---

diff --git a/README.rst b/README.rst
index 49abe66..978de41 100644
--- a/README.rst
+++ b/README.rst
@@ -1,19 +1,13 @@
-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/.
 
 Testing
 -------

From 2e8a54aaa425c2145f26a8b00cf103373cc317f5 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 23 2018 16:41:38 +0000
Subject: [PATCH 46/48] using tox for testing doesn't work


With modulemd as the Python wrapper around libmodulemd (using gobject
introspection), tox as it is configured fails to set up the virtualenv
properly. Don't mention it in the README and remove its configuration.

---

diff --git a/README.rst b/README.rst
index 978de41..b976f00 100644
--- a/README.rst
+++ b/README.rst
@@ -8,12 +8,3 @@ modulemd:
         A Python wrapper around the libmodulemd library for manipulation of the
         module metadata format. API documentation is available at
         http://modulemd.readthedocs.org/.
-
-Testing
--------
-
-.. code-block:: bash
-
-   $ tox -e py27,py35
-
-*See tox.ini for additional environments.*
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index 410db77..0000000
--- a/tox.ini
+++ /dev/null
@@ -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

From d00fd31f61eacfc507f873fffe6d30f3403e20f9 Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 24 2018 13:08:58 +0000
Subject: [PATCH 47/48] instead of tox, describe running unit tests at least


---

diff --git a/README.rst b/README.rst
index b976f00..bcf07b6 100644
--- a/README.rst
+++ b/README.rst
@@ -8,3 +8,10 @@ modulemd:
         A Python wrapper around the libmodulemd library for manipulation of the
         module metadata format. API documentation is available at
         http://modulemd.readthedocs.org/.
+
+Testing
+-------
+
+.. code-block:: bash
+
+   $ python setup.py test

From 775984542f6cd5a991040fbe4b5c9fc228bc79db Mon Sep 17 00:00:00 2001
From: Nils Philippsen <nils@redhat.com>
Date: Jan 24 2018 13:08:58 +0000
Subject: [PATCH 48/48] link libmodulemd spec.yaml


---

diff --git a/README.rst b/README.rst
index bcf07b6..351bc8d 100644
--- a/README.rst
+++ b/README.rst
@@ -9,6 +9,11 @@ modulemd:
         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
 -------
 

