Repository: pmxbot/pmxbot (language: python)

Issue:
pmxbot uses deprecated pkg_resources namespace packages



Discussion / hints:

Fix patch:
diff --git a/newsfragments/108.feature.rst b/newsfragments/108.feature.rst
new file mode 100644
index 00000000..f68cee4e
--- /dev/null
+++ b/newsfragments/108.feature.rst
@@ -0,0 +1,1 @@
+``pmxbot`` is now a native namespace package and no longer relies on ``pkg_resources`` for that old technique for a namespace package.
diff --git a/pmxbot/__init__.py b/pmxbot/__init__.py
deleted file mode 100644
index de40ea7c..00000000
--- a/pmxbot/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-__import__('pkg_resources').declare_namespace(__name__)
diff --git a/setup.cfg b/setup.cfg
index dcd57b53..14c2f788 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -18,7 +18,6 @@ classifiers =
 
 [options]
 packages = find_namespace:
-namespace_packages = pmxbot
 include_package_data = true
 python_requires = >=3.8
 install_requires =
diff --git a/tox.ini b/tox.ini
index 73ebec27..d03f6b3c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,6 +4,7 @@ toxworkdir={env:TOX_WORK_DIR:.tox}
 
 [testenv]
 deps =
+	pytest @ git+https://github.com/jaraco/pytest@pmxbot-108
 setenv =
 	PYTHONWARNDEFAULTENCODING = 1
 commands =


Test patch:
diff --git a/pytest.ini b/pytest.ini
index 2540048b..8963463d 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,6 +1,8 @@
 [pytest]
 norecursedirs=dist build .tox .eggs
-addopts=--doctest-modules
+addopts=
+	--doctest-modules
+	--import-mode importlib
 filterwarnings=
 	## upstream
 
diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py
index 6e68aa62..e69de29b 100644
--- a/tests/functional/__init__.py
+++ b/tests/functional/__init__.py
@@ -1,148 +0,0 @@
-import subprocess
-import os
-import sys
-import time
-import sqlite3
-import datetime
-import urllib.parse
-
-import irc.client
-import pytest
-import tempora.timing
-
-import pmxbot.dictlib
-
-
-class TestingClient:
-    """
-    A simple client simulating a user other than the pmxbot
-    """
-
-    def __init__(self, server, port, nickname):
-        self.reactor = irc.client.Reactor()
-        self.c = self.reactor.server()
-        self.c.connect(server, port, nickname)
-        self.reactor.process_once(0.1)
-        self.channels = set()
-
-    def join(self, channel):
-        self.c.join(channel)
-        self.channels.add(channel)
-
-    def send_message(self, channel, message):
-        if channel not in self.channels:
-            self.join(channel)
-        self.c.privmsg(channel, message)
-        time.sleep(0.05)
-
-
-class PmxbotHarness:
-    config = pmxbot.dictlib.ConfigDict(
-        server_port=6668,
-        bot_nickname='pmxbotTest',
-        log_channels=['#logged'],
-        other_channels=['#inane'],
-        database="sqlite:tests/functional/pmxbot.sqlite",
-    )
-
-    @classmethod
-    def setup_class(cls):
-        """Start an IRC server, launch the bot, and ask it to do stuff"""
-        path = os.path.dirname(os.path.abspath(__file__))
-        cls.config_fn = os.path.join(path, 'testconf.yaml')
-        cls.config.to_yaml(cls.config_fn)
-        cls.dbfile = urllib.parse.urlparse(cls.config['database']).path
-        cls.db = sqlite3.connect(cls.dbfile)
-        env = os.environ.copy()
-        # copy the current sys.path to PYTHONPATH so subprocesses have access
-        #  to libs pulled by tests_require
-        env['PYTHONPATH'] = os.pathsep.join(sys.path)
-        try:
-            cmd = [sys.executable, '-m', 'irc.server', '-p', '6668', '-l', 'debug']
-            cls.server = subprocess.Popen(cmd, env=env)
-        except OSError:
-            pytest.skip("Unable to launch irc server.")
-        time.sleep(0.5)
-        # add './plugins' to the path so we get some pmxbot commands specific
-        #  for testing.
-        plugins = os.path.join(path, 'plugins')
-        env['PYTHONPATH'] = os.pathsep.join([plugins, env['PYTHONPATH']])
-        try:
-            # Launch pmxbot using Python directly (rather than through
-            #  the console entry point, which can't be properly
-            #  .terminate()d on Windows.
-            cmd = [sys.executable, '-m', 'pmxbot', cls.config_fn]
-            cls.bot = subprocess.Popen(cmd, env=env)
-        except OSError:
-            pytest.skip("Unable to launch pmxbot (pmxbot must be installed)")
-        cls.wait_for_tables()
-        cls.wait_for_output()
-        if cls.bot.poll() is not None:
-            pytest.skip("Bot did not start up properly")
-        cls.client = TestingClient('localhost', 6668, 'testingbot')
-
-    @classmethod
-    def wait_for_output(cls):
-        """
-        Wait for 'Running with config' in cls.bot.output
-        """
-        if cls.bot.poll() is not None:
-            return
-        # stubbed
-        time.sleep(5)
-
-    @classmethod
-    def wait_for_tables(cls, timeout=30):
-        watch = tempora.timing.Stopwatch()
-        while watch.split() < datetime.timedelta(seconds=timeout):
-            try:
-                cls.check_logs('#check')
-                return
-            except Exception:
-                # short-circuit if the bot has stopped
-                if cls.bot.poll() is not None:
-                    return
-                time.sleep(0.2)
-
-    @classmethod
-    def check_logs(cls, channel='', nick='', message=''):
-        if channel.startswith('#'):
-            channel = channel[1:]
-        time.sleep(0.2)
-        cursor = cls.db.cursor()
-        query = (
-            "select * from logs where 1=1"
-            + " and channel = :channel" * bool(channel)
-            + " and nick = :nick" * bool(nick)
-            + " and message = :message" * bool(message)
-        )
-        cursor.execute(query, locals())
-        res = cursor.fetchall()
-        print(res)
-        return len(res) >= 1
-
-    @classmethod
-    def teardown_class(cls):
-        os.remove(cls.config_fn)
-        if hasattr(cls, 'bot') and not cls.bot.poll():
-            cls.bot.terminate()
-            cls.bot.wait()
-        if hasattr(cls, 'server') and cls.server.poll() is None:
-            cls.server.terminate()
-            cls.server.wait()
-        if hasattr(cls, 'db'):
-            cls.db.rollback()
-            cls.db.close()
-            del cls.db
-        if hasattr(cls, 'client'):
-            del cls.client
-        # wait up to 10 seconds for the file to be removable
-        for x in range(100):
-            try:
-                if os.path.isfile(cls.dbfile):
-                    os.remove(cls.dbfile)
-                break
-            except OSError:
-                time.sleep(0.1)
-        else:
-            raise RuntimeError('Could not remove log db', cls.dbfile)
diff --git a/tests/functional/harness.py b/tests/functional/harness.py
new file mode 100644
index 00000000..6e68aa62
--- /dev/null
+++ b/tests/functional/harness.py
@@ -0,0 +1,148 @@
+import subprocess
+import os
+import sys
+import time
+import sqlite3
+import datetime
+import urllib.parse
+
+import irc.client
+import pytest
+import tempora.timing
+
+import pmxbot.dictlib
+
+
+class TestingClient:
+    """
+    A simple client simulating a user other than the pmxbot
+    """
+
+    def __init__(self, server, port, nickname):
+        self.reactor = irc.client.Reactor()
+        self.c = self.reactor.server()
+        self.c.connect(server, port, nickname)
+        self.reactor.process_once(0.1)
+        self.channels = set()
+
+    def join(self, channel):
+        self.c.join(channel)
+        self.channels.add(channel)
+
+    def send_message(self, channel, message):
+        if channel not in self.channels:
+            self.join(channel)
+        self.c.privmsg(channel, message)
+        time.sleep(0.05)
+
+
+class PmxbotHarness:
+    config = pmxbot.dictlib.ConfigDict(
+        server_port=6668,
+        bot_nickname='pmxbotTest',
+        log_channels=['#logged'],
+        other_channels=['#inane'],
+        database="sqlite:tests/functional/pmxbot.sqlite",
+    )
+
+    @classmethod
+    def setup_class(cls):
+        """Start an IRC server, launch the bot, and ask it to do stuff"""
+        path = os.path.dirname(os.path.abspath(__file__))
+        cls.config_fn = os.path.join(path, 'testconf.yaml')
+        cls.config.to_yaml(cls.config_fn)
+        cls.dbfile = urllib.parse.urlparse(cls.config['database']).path
+        cls.db = sqlite3.connect(cls.dbfile)
+        env = os.environ.copy()
+        # copy the current sys.path to PYTHONPATH so subprocesses have access
+        #  to libs pulled by tests_require
+        env['PYTHONPATH'] = os.pathsep.join(sys.path)
+        try:
+            cmd = [sys.executable, '-m', 'irc.server', '-p', '6668', '-l', 'debug']
+            cls.server = subprocess.Popen(cmd, env=env)
+        except OSError:
+            pytest.skip("Unable to launch irc server.")
+        time.sleep(0.5)
+        # add './plugins' to the path so we get some pmxbot commands specific
+        #  for testing.
+        plugins = os.path.join(path, 'plugins')
+        env['PYTHONPATH'] = os.pathsep.join([plugins, env['PYTHONPATH']])
+        try:
+            # Launch pmxbot using Python directly (rather than through
+            #  the console entry point, which can't be properly
+            #  .terminate()d on Windows.
+            cmd = [sys.executable, '-m', 'pmxbot', cls.config_fn]
+            cls.bot = subprocess.Popen(cmd, env=env)
+        except OSError:
+            pytest.skip("Unable to launch pmxbot (pmxbot must be installed)")
+        cls.wait_for_tables()
+        cls.wait_for_output()
+        if cls.bot.poll() is not None:
+            pytest.skip("Bot did not start up properly")
+        cls.client = TestingClient('localhost', 6668, 'testingbot')
+
+    @classmethod
+    def wait_for_output(cls):
+        """
+        Wait for 'Running with config' in cls.bot.output
+        """
+        if cls.bot.poll() is not None:
+            return
+        # stubbed
+        time.sleep(5)
+
+    @classmethod
+    def wait_for_tables(cls, timeout=30):
+        watch = tempora.timing.Stopwatch()
+        while watch.split() < datetime.timedelta(seconds=timeout):
+            try:
+                cls.check_logs('#check')
+                return
+            except Exception:
+                # short-circuit if the bot has stopped
+                if cls.bot.poll() is not None:
+                    return
+                time.sleep(0.2)
+
+    @classmethod
+    def check_logs(cls, channel='', nick='', message=''):
+        if channel.startswith('#'):
+            channel = channel[1:]
+        time.sleep(0.2)
+        cursor = cls.db.cursor()
+        query = (
+            "select * from logs where 1=1"
+            + " and channel = :channel" * bool(channel)
+            + " and nick = :nick" * bool(nick)
+            + " and message = :message" * bool(message)
+        )
+        cursor.execute(query, locals())
+        res = cursor.fetchall()
+        print(res)
+        return len(res) >= 1
+
+    @classmethod
+    def teardown_class(cls):
+        os.remove(cls.config_fn)
+        if hasattr(cls, 'bot') and not cls.bot.poll():
+            cls.bot.terminate()
+            cls.bot.wait()
+        if hasattr(cls, 'server') and cls.server.poll() is None:
+            cls.server.terminate()
+            cls.server.wait()
+        if hasattr(cls, 'db'):
+            cls.db.rollback()
+            cls.db.close()
+            del cls.db
+        if hasattr(cls, 'client'):
+            del cls.client
+        # wait up to 10 seconds for the file to be removable
+        for x in range(100):
+            try:
+                if os.path.isfile(cls.dbfile):
+                    os.remove(cls.dbfile)
+                break
+            except OSError:
+                time.sleep(0.1)
+        else:
+            raise RuntimeError('Could not remove log db', cls.dbfile)
diff --git a/tests/functional/test_exceptions.py b/tests/functional/test_exceptions.py
index b8d768fb..1a1c7421 100644
--- a/tests/functional/test_exceptions.py
+++ b/tests/functional/test_exceptions.py
@@ -1,6 +1,6 @@
 import time
 
-from tests.functional import PmxbotHarness
+from .harness import PmxbotHarness
 
 
 class TestPmxbotExceptions(PmxbotHarness):
diff --git a/tests/functional/test_logging.py b/tests/functional/test_logging.py
index b75835cf..a915dc2c 100644
--- a/tests/functional/test_logging.py
+++ b/tests/functional/test_logging.py
@@ -1,7 +1,7 @@
 import time
 import uuid
 
-from tests.functional import PmxbotHarness
+from .harness import PmxbotHarness
 
 
 class TestPmxbotLog(PmxbotHarness):
diff --git a/tests/functional/test_messages.py b/tests/functional/test_messages.py
index 64d902c2..ed7a2b92 100644
--- a/tests/functional/test_messages.py
+++ b/tests/functional/test_messages.py
@@ -1,6 +1,6 @@
 import time
 
-from tests.functional import PmxbotHarness
+from .harness import PmxbotHarness
 
 
 class TestPmxbotMessages(PmxbotHarness):
diff --git a/tests/functional/test_regexps.py b/tests/functional/test_regexps.py
index 42478a77..bd001a0b 100644
--- a/tests/functional/test_regexps.py
+++ b/tests/functional/test_regexps.py
@@ -1,6 +1,6 @@
 import time
 
-from tests.functional import PmxbotHarness
+from .harness import PmxbotHarness
 
 
 class TestPmxbotRegexp(PmxbotHarness):