diff --git a/.modulemd-tools.checksum b/.modulemd-tools.checksum
new file mode 100644
index 0000000000000000000000000000000000000000..45ff1f161390a13ee8d7e1aec0d3b1e87c27bc4c
--- /dev/null
+++ b/.modulemd-tools.checksum
@@ -0,0 +1 @@
+03f8c537b14fc22d7e75e24c6d7b76b09bb71db055f09c4e9ae4edb2613ebe71
diff --git a/.modulemd-tools.metadata b/.modulemd-tools.metadata
index b3402facc6b5f8d242848ab9d00e407ff9a88261..c88322067d41422a864cbd3293d3ea8f50ff066e 100644
--- a/.modulemd-tools.metadata
+++ b/.modulemd-tools.metadata
@@ -1 +1 @@
-de632f2d9259df3921deeb43b33fae20222cb54f SOURCES/modulemd-tools-0.9.tar.gz
+8b5dc196c11bc31ece5a5fa38aa76d7c3d19c3fb9496dbec43656781f0e43475 SOURCES/modulemd-tools-0.9.tar.gz
diff --git a/SOURCES/0001-dir2module-generate-also-profiles-and-modulemd-defau.patch b/SOURCES/0001-dir2module-generate-also-profiles-and-modulemd-defau.patch
new file mode 100644
index 0000000000000000000000000000000000000000..1a6a3438c6fb42288292c7742dbaf29ab2e89f4c
--- /dev/null
+++ b/SOURCES/0001-dir2module-generate-also-profiles-and-modulemd-defau.patch
@@ -0,0 +1,193 @@
+From 908b48d28cf68946dfe2f309d73f3f2a12efe021 Mon Sep 17 00:00:00 2001
+From: Jakub Kadlcik <frostyx@email.cz>
+Date: Thu, 19 Aug 2021 00:33:59 +0200
+Subject: [PATCH] dir2module: generate also profiles and modulemd-defaults file
+
+---
+ dir2module/dir2module/dir2module.py | 102 ++++++++++++++++++++++++----
+ dir2module/tests/conftest.py        |   2 +-
+ 2 files changed, 90 insertions(+), 14 deletions(-)
+
+diff --git a/dir2module/dir2module/dir2module.py b/dir2module/dir2module/dir2module.py
+index 9902aec..6643a83 100755
+--- a/dir2module/dir2module/dir2module.py
++++ b/dir2module/dir2module/dir2module.py
+@@ -20,13 +20,13 @@ gi.require_version("Modulemd", "2.0")
+ from gi.repository import Modulemd  # noqa: E402
+ 
+ 
+-class Module(object):
++class ModuleBase:
+     """
+-    Provide a high-level interface for representing modules and yaml generation
+-    based on their values.
++    Base class for modulemd things
+     """
++
+     def __init__(self, name, stream, version, context, arch, summary,
+-                 description, module_license, licenses, package_nevras, requires):
++                 description, module_license, licenses, packages, requires):
+         self.name = name
+         self.stream = stream
+         self.version = version
+@@ -36,18 +36,69 @@ class Module(object):
+         self.description = description
+         self.module_license = module_license
+         self.licenses = licenses
+-        self.package_nevras = package_nevras
++        self.packages = packages
+         self.requires = requires
+ 
++    @property
++    def filename_format(self):
++        """
++        String format for the modulemd filename. It can contain the following
++        variables:
++        {N} - Module name
++        {S} - Module stream name
++        {V} - Module version
++        {C} - Module context
++        {A} - Module architecture
++        """
++        raise NotImplementedError
++
++    def dumps(self):
++        """
++        Generate YAML based on input parameters and return it as a string
++        """
++        raise NotImplementedError
++
+     @property
+     def filename(self):
+         """
+         Generate filename for a module yaml
+         """
+-        return "{N}:{S}:{V}:{C}:{A}.modulemd.yaml".format(
++        return self.filename_format.format(
+             N=self.name, S=self.stream, V=self.version,
+             C=self.context, A=self.arch)
+ 
++    def dump(self):
++        """
++        Generate modulemd yaml based on input parameters write it into file
++        """
++        with open(self.filename, "w") as moduleyaml:
++            moduleyaml.write(self.dumps())
++
++    @property
++    def package_names(self):
++        """
++        Return the list of unique package names within this module
++        """
++        return {package.header.name for package in self.packages}
++
++    @property
++    def package_nevras(self):
++        """
++        Return the list of unique package NEVRAs within this module
++        """
++        return {package.nevra for package in self.packages}
++
++
++class Module(ModuleBase):
++    """
++    Provide a high-level interface for representing modules and yaml generation
++    based on their values.
++    """
++
++    @property
++    def filename_format(self):
++        return "{N}:{S}:{V}:{C}:{A}.modulemd.yaml"
++
+     def dumps(self):
+         """
+         Generate modulemd yaml based on input parameters and return it as a string
+@@ -70,16 +121,37 @@ class Module(object):
+             dependencies.add_runtime_stream(depname, depstream)
+         mod_stream.add_dependencies(dependencies)
+ 
++        profile = Modulemd.Profile.new("common")
++        for pkgname in self.package_names:
++            profile.add_rpm(pkgname)
++        mod_stream.add_profile(profile)
++
+         index = Modulemd.ModuleIndex.new()
+         index.add_module_stream(mod_stream)
+         return index.dump_to_string()
+ 
+-    def dump(self):
++
++class ModuleDefaults(ModuleBase):
++    """
++    Provide a high-level interface for representing modulemd defaults files
++    """
++
++    @property
++    def filename_format(self):
++        return "{N}:{S}:{V}:{C}:{A}.modulemd-defaults.yaml"
++
++    def dumps(self):
+         """
+-        Generate modulemd yaml based on input parameters write it into file
++        Generate modulemd_defaults yaml based on input parameters and return it
++        as a string
+         """
+-        with open(self.filename, "w") as moduleyaml:
+-            moduleyaml.write(self.dumps())
++        mod_defaults = Modulemd.DefaultsV1.new(self.name)
++        mod_defaults.set_default_stream(self.stream)
++        mod_defaults.add_default_profile_for_stream(self.stream, "common")
++
++        index = Modulemd.ModuleIndex.new()
++        index.add_defaults(mod_defaults)
++        return index.dump_to_string()
+ 
+ 
+ class Package(object):
+@@ -220,7 +292,6 @@ def main():
+ 
+     packages = [Package(package) for package in packages]
+     licenses = {package.license for package in packages}
+-    nevras = {package.nevra for package in packages}
+ 
+     requires = parse_dependencies(args.requires)
+     description = args.description \
+@@ -238,8 +309,10 @@ def main():
+         raise RuntimeError("All packages need to contain the `modularitylabel` header. "
+                            "To suppress this constraint, use `--force` parameter")
+ 
+-    module = Module(name, stream, version, context, arch, args.summary,
+-                    description, args.license, licenses, nevras, requires)
++    modargs = [name, stream, version, context, arch, args.summary, description,
++               args.license, licenses, packages, requires]
++    module = Module(*modargs)
++    module_defaults = ModuleDefaults(*modargs)
+ 
+     if args.stdout:
+         print(module.dumps())
+@@ -247,6 +320,9 @@ def main():
+         module.dump()
+         print("Created {0}".format(module.filename))
+ 
++        module_defaults.dump()
++        print("Created {0}".format(module_defaults.filename))
++
+ 
+ if __name__ == "__main__":
+     try:
+diff --git a/dir2module/tests/conftest.py b/dir2module/tests/conftest.py
+index 9309b44..c6956cb 100644
+--- a/dir2module/tests/conftest.py
++++ b/dir2module/tests/conftest.py
+@@ -15,7 +15,7 @@ def dummy_module():
+         'description': 'One dummy module for your tests',
+         'module_license': 'No License',
+         'licenses': [],
+-        'package_nevras': [],
++        'packages': [],
+         'requires': {}
+     }
+ 
+-- 
+2.41.0
+
diff --git a/SOURCES/0002-repo2module-don-t-traceback-because-of-a-modular-SRP.patch b/SOURCES/0002-repo2module-don-t-traceback-because-of-a-modular-SRP.patch
new file mode 100644
index 0000000000000000000000000000000000000000..ee921ee4997c1446896b07d3e82784e711c0d231
--- /dev/null
+++ b/SOURCES/0002-repo2module-don-t-traceback-because-of-a-modular-SRP.patch
@@ -0,0 +1,30 @@
+From 1188c8215955c14fadb1f17c2b55f4135db2a224 Mon Sep 17 00:00:00 2001
+From: Jakub Kadlcik <frostyx@email.cz>
+Date: Tue, 2 May 2023 00:12:39 +0200
+Subject: [PATCH 2/4] repo2module: don't traceback because of a modular SRPM in
+ the repo
+
+Fix RHBZ 2186223
+---
+ repo2module/repo2module/cli.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/repo2module/repo2module/cli.py b/repo2module/repo2module/cli.py
+index 1d4ce27..44dd24c 100644
+--- a/repo2module/repo2module/cli.py
++++ b/repo2module/repo2module/cli.py
+@@ -61,6 +61,11 @@ def get_source_packages(packages):
+     """
+     source_packages = set()
+     for pkg in packages:
++        # In this case, the `pkg` is a SRPM file
++        if not pkg.rpm_sourcerpm:
++            source_packages.add(pkg.name)
++            continue
++
+         # Get the source RPM NEVRA without the trailing ".rpm"
+         subject = Subject(pkg.rpm_sourcerpm[:-4])
+ 
+-- 
+2.41.0
+
diff --git a/SOURCES/0003-createrepo-replace-deprecated-LooseVersion.patch b/SOURCES/0003-createrepo-replace-deprecated-LooseVersion.patch
new file mode 100644
index 0000000000000000000000000000000000000000..8f40efacbfe065cb3a81904d5a0f722e37ffb783
--- /dev/null
+++ b/SOURCES/0003-createrepo-replace-deprecated-LooseVersion.patch
@@ -0,0 +1,133 @@
+From f3435b9a9f5ddff0d4593dbda7c5da592ea31f61 Mon Sep 17 00:00:00 2001
+From: Marek Kulik <mkulik@redhat.com>
+Date: Wed, 20 Apr 2022 09:38:51 +0200
+Subject: [PATCH 3/4] createrepo: replace deprecated LooseVersion
+
+DeprecationWarning: The distutils package is
+deprecated and slated for removal in Python 3.12
+---
+ .../createrepo_mod/createrepo_mod.py          | 13 +++++--
+ modulemd_tools/modulemd_tools/yaml.py         | 39 +++++++++++++++++++
+ modulemd_tools/tests/test_yaml.py             |  9 ++++-
+ 3 files changed, 55 insertions(+), 6 deletions(-)
+
+diff --git a/createrepo_mod/createrepo_mod/createrepo_mod.py b/createrepo_mod/createrepo_mod/createrepo_mod.py
+index ee078f7..4e0eb71 100755
+--- a/createrepo_mod/createrepo_mod/createrepo_mod.py
++++ b/createrepo_mod/createrepo_mod/createrepo_mod.py
+@@ -16,11 +16,16 @@ https://docs.fedoraproject.org/en-US/modularity/hosting-modules/
+ """
+ 
+ 
++import argparse
+ import os
+-import sys
+ import subprocess
+-import argparse
+-from distutils.version import LooseVersion
++import sys
++
++# python3-packaging in not available in RHEL 8.x
++try:
++    from packaging.version import Version
++except ModuleNotFoundError:
++    from distutils.version import LooseVersion as Version
+ 
+ import gi
+ gi.require_version("Modulemd", "2.0")
+@@ -99,7 +104,7 @@ def createrepo_c_with_builtin_module_support():
+     """
+     cmd = ["rpm", "-q", "createrepo_c", "--queryformat", "%{VERSION}"]
+     createrepo_c_version = subprocess.check_output(cmd).decode("utf-8")
+-    return LooseVersion(createrepo_c_version) >= LooseVersion("0.16.1")
++    return Version(createrepo_c_version) >= Version("0.16.1")
+ 
+ 
+ def main():
+diff --git a/modulemd_tools/modulemd_tools/yaml.py b/modulemd_tools/modulemd_tools/yaml.py
+index 43f314f..ac644b6 100644
+--- a/modulemd_tools/modulemd_tools/yaml.py
++++ b/modulemd_tools/modulemd_tools/yaml.py
+@@ -8,6 +8,12 @@ import os
+ import gi
+ import yaml
+ 
++# python3-packaging in not available in RHEL 8.x
++try:
++    from packaging.version import Version
++except ModuleNotFoundError:
++    from distutils.version import StrictVersion as Version
++
+ gi.require_version("Modulemd", "2.0")
+ from gi.repository import Modulemd  # noqa: E402
+ 
+@@ -307,3 +313,36 @@ def _stream2yaml(mod_stream):
+         return idx.dump_to_string()
+     except gi.repository.GLib.GError as ex:
+         raise RuntimeError(ex.message)
++
++
++def _modulemd_read_packager_string(mod_yaml, name=None, stream=None):
++    """
++    For the time being we happen to be in a transition state when
++    `Modulemd.ModuleStream.read_string` is deprecated and throws warnings on
++    Fedora but we still use old libmodulemd (2.9.4) on RHEL8, which doesn't
++    provide its replacement in the form of `Modulemd.read_packager_string`.
++    """
++    if Version(Modulemd.get_version()) < Version("2.11"):
++        mod_stream = Modulemd.ModuleStreamV2.new(name, stream)
++        mod_stream = mod_stream.read_string(mod_yaml, True, name, stream)
++        return mod_stream
++
++    return Modulemd.read_packager_string(mod_yaml, name, stream)
++
++
++def _modulestream_upgrade_ext(mod_stream, version):
++    """
++    For the time being we happen to be in a transition state when
++    `Modulemd.ModuleStream.upgrade` is deprecated and throws warnings on
++    Fedora but we still use old libmodulemd (2.9.4) on RHEL8, which doesn't
++    provide its replacement in the form of `Modulemd.ModuleStream.upgrade_ext`.
++    """
++    if Version(Modulemd.get_version()) < Version("2.10"):
++        return mod_stream.upgrade(version)
++
++    mod_upgraded = mod_stream.upgrade_ext(version)
++    return mod_upgraded.get_stream_by_NSVCA(
++        mod_stream.get_stream_name(),
++        mod_stream.get_version(),
++        mod_stream.get_context(),
++        mod_stream.get_arch())
+diff --git a/modulemd_tools/tests/test_yaml.py b/modulemd_tools/tests/test_yaml.py
+index 8090a2b..f51a330 100644
+--- a/modulemd_tools/tests/test_yaml.py
++++ b/modulemd_tools/tests/test_yaml.py
+@@ -2,10 +2,15 @@ import os
+ import unittest
+ from unittest import mock
+ import yaml
+-from distutils.version import LooseVersion
+ from modulemd_tools.yaml import (is_valid, validate, create, update, dump,
+                                  upgrade, _yaml2stream, _stream2yaml)
+ 
++# python3-packaging in not available in RHEL 8.x
++try:
++    from packaging.version import Version
++except ModuleNotFoundError:
++    from distutils.version import LooseVersion as Version
++
+ import gi
+ gi.require_version("Modulemd", "2.0")
+ from gi.repository import Modulemd  # noqa: E402
+@@ -19,7 +24,7 @@ def old_libmodulemd():
+     skip those few test on EPEL8 until it receives an update.
+     See also `080e2bb`
+     """
+-    return LooseVersion(Modulemd.get_version()) < LooseVersion("2.11.1")
++    return Version(Modulemd.get_version()) < Version("2.11.1")
+ 
+ 
+ class TestYaml(unittest.TestCase):
+-- 
+2.41.0
+
diff --git a/SOURCES/0004-modulemd_tools-fix-tests-for-new-libmodulemd-version.patch b/SOURCES/0004-modulemd_tools-fix-tests-for-new-libmodulemd-version.patch
new file mode 100644
index 0000000000000000000000000000000000000000..1c64afd9c0374480ef6e2244f3ca57aac7b73e52
--- /dev/null
+++ b/SOURCES/0004-modulemd_tools-fix-tests-for-new-libmodulemd-version.patch
@@ -0,0 +1,47 @@
+From 0407a6af2f0c59e9619418a606d5d00183d40693 Mon Sep 17 00:00:00 2001
+From: Jakub Kadlcik <frostyx@email.cz>
+Date: Tue, 13 Jun 2023 18:19:15 +0200
+Subject: [PATCH 4/4] modulemd_tools: fix tests for new libmodulemd version
+ 2.15.0
+
+---
+ modulemd_tools/tests/test_yaml.py | 15 +++++++++++++--
+ 1 file changed, 13 insertions(+), 2 deletions(-)
+
+diff --git a/modulemd_tools/tests/test_yaml.py b/modulemd_tools/tests/test_yaml.py
+index f51a330..2f8b4d2 100644
+--- a/modulemd_tools/tests/test_yaml.py
++++ b/modulemd_tools/tests/test_yaml.py
+@@ -27,6 +27,10 @@ def old_libmodulemd():
+     return Version(Modulemd.get_version()) < Version("2.11.1")
+ 
+ 
++def min_libmodulemd_version(version):
++    return Version(Modulemd.get_version()) >= Version(version)
++
++
+ class TestYaml(unittest.TestCase):
+ 
+     def test_is_valid(self):
+@@ -57,9 +61,16 @@ class TestYaml(unittest.TestCase):
+         self.assertEqual(mod1["version"], 2)
+         self.assertEqual(mod1["data"]["name"], "foo")
+         self.assertEqual(mod1["data"]["stream"], "stable")
+-        self.assertEqual(mod1["data"]["summary"], None)
+         self.assertEqual(mod1["data"]["description"], "")
+-        self.assertEqual(mod1["data"]["license"]["module"], [None])
++
++        # Between libmodulemd version 2.14.0 and 2.15.0 a change in `None`
++        # vs empty string happened
++        if min_libmodulemd_version("2.15.0"):
++            self.assertEqual(mod1["data"]["summary"], "")
++            self.assertEqual(mod1["data"]["license"]["module"], [""])
++        else:
++            self.assertEqual(mod1["data"]["summary"], None)
++            self.assertEqual(mod1["data"]["license"]["module"], [None])
+ 
+     def test_update_after_build(self):
+         """
+-- 
+2.41.0
+
diff --git a/SPECS/modulemd-tools.spec b/SPECS/modulemd-tools.spec
index 0c9249f3e3d20baeb112f184bf609a9c157263b0..4254991925c30a5c833336430c76d91b3ca3f3c2 100644
--- a/SPECS/modulemd-tools.spec
+++ b/SPECS/modulemd-tools.spec
@@ -1,6 +1,6 @@
 Name: modulemd-tools
 Version: 0.9
-Release: 3%{?dist}
+Release: 5%{?dist}
 Summary: Collection of tools for parsing and generating modulemd YAML files
 License: MIT
 BuildArch: noarch
@@ -8,6 +8,18 @@ BuildArch: noarch
 URL: https://github.com/rpm-software-management/modulemd-tools
 Source0: https://github.com/rpm-software-management/modulemd-tools/archive/%{version}/%{name}-%{version}.tar.gz
 
+# https://github.com/rpm-software-management/modulemd-tools/commit/195df77
+Patch0: 0001-dir2module-generate-also-profiles-and-modulemd-defau.patch
+
+# https://github.com/rpm-software-management/modulemd-tools/commit/0d718ca
+Patch1: 0002-repo2module-don-t-traceback-because-of-a-modular-SRP.patch
+
+# https://github.com/rpm-software-management/modulemd-tools/commit/cd04198
+Patch2: 0003-createrepo-replace-deprecated-LooseVersion.patch
+
+# https://github.com/rpm-software-management/modulemd-tools/commit/ac3b173
+Patch3: 0004-modulemd_tools-fix-tests-for-new-libmodulemd-version.patch
+
 BuildRequires: createrepo_c
 BuildRequires: argparse-manpage
 BuildRequires: python3-devel
@@ -50,7 +62,7 @@ modulemd-generate-macros - Generate module-build-macros SRPM package, which is
 
 
 %prep
-%setup -q
+%autosetup -p1
 
 
 %build
@@ -156,6 +168,17 @@ cd ..
 
 
 %changelog
+* Sun Jul 30 2023 Jakub Kadlcik <jkadlcik@redhat.com> - 0.9-5
+- Don't traceback because of a modular SRPM in the repo
+  Related: rhbz#2227436
+- Replace deprecated LooseVersion
+- Fix tests for new libmodulemd version 2.15.0
+
+* Fri Jul 21 2023 Jakub Kadlcik <jkadlcik@redhat.com> - 0.9-4
+- Generate profiles section and modulemd-defaults file
+  Related: rhbz#1801747
+- Use autosetup to automatically apply patches
+
 * Mon Aug 09 2021 Mohan Boddu <mboddu@redhat.com> - 0.9-3
 - Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
   Related: rhbz#1991688
@@ -181,7 +204,7 @@ cd ..
 - Drop libmodulemd dependency in favor of python3-libmodulemd
 
 * Sun Nov 22 2020 Jakub Kadlcik <frostyx@email.cz> 0.6-1
-- Generate manpages for all tools in this repository 
+- Generate manpages for all tools in this repository
 - modulemd-generate-macros: add a tool for generating module-build-macros
 - modulemd_tools: add the first pieces of a python library (for internal usage only)