Autodiscover didn't work

hello, I followed the documentation, but it didn’t work!

message:

➜  autodiscover sanic server.app  --debug
Starting in v22.3, --debug will no longer automatically run the auto-reloader.
  Switch to --dev to continue using that functionality.
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/log.py:89: DeprecationWarning: [DEPRECATION v22.6] The register argument is deprecated and will stop working in v22.6. After v22.6 all apps will be added to the Sanic app registry.
  warn(version_info + message, DeprecationWarning)
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.9/bin/sanic", line 8, in <module>
    sys.exit(main())
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/__main__.py", line 12, in main
    cli.run()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/cli/app.py", line 75, in run
    app = self._get_app()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/cli/app.py", line 127, in _get_app
    module = import_module(module_name)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/Users/bitmain/Desktop/Code/sanic/autodiscover/server.py", line 8, in <module>
    autodiscover(
  File "/Users/bitmain/Desktop/Code/sanic/autodiscover/utility.py", line 32, in autodiscover
    base = Path(module.__file__).parent
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pathlib.py", line 1082, in __new__
    self = cls._from_parts(args, init=False)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pathlib.py", line 707, in _from_parts
    drv, root, parts = self._parse_args(args)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pathlib.py", line 691, in _parse_args
    a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not NoneType

thank you

Please post your code. It looks like something may be missing.

➜  autodiscover python3 --version
Python 3.9.10
➜  autodiscover sanic --version
Sanic 21.12.1; Routing 0.7.2

➜  autodiscover cat server.py 
from sanic import Sanic
from sanic.response import empty

import blueprints
from utility import autodiscover

app = Sanic("auto", register=True)
autodiscover(
    app,
    blueprints,
    "parent.child",
    "listeners.something",
    recursive=True,
)

app.route("/")(lambda _: empty())

➜  autodiscover cat utility.py 
from glob import glob
from importlib import import_module, util
from inspect import getmembers
from pathlib import Path
from types import ModuleType
from typing import Union

from sanic.blueprints import Blueprint


def autodiscover(
    app, *module_names: Union[str, ModuleType], recursive: bool = False
):
    mod = app.__module__
    blueprints = set()
    _imported = set()

    def _find_bps(module):
        nonlocal blueprints

        for _, member in getmembers(module):
            if isinstance(member, Blueprint):
                blueprints.add(member)

    for module in module_names:
        if isinstance(module, str):
            module = import_module(module, mod)
            _imported.add(module.__file__)
        _find_bps(module)

        if recursive:
            base = Path(module.__file__).parent
            for path in glob(f"{base}/**/*.py", recursive=True):
                if path not in _imported:
                    name = "module"
                    if "__init__" in path:
                        *_, name, __ = path.split("/")
                    spec = util.spec_from_file_location(name, path)
                    specmod = util.module_from_spec(spec)
                    _imported.add(path)
                    spec.loader.exec_module(specmod)
                    _find_bps(specmod)

    for bp in blueprints:
        app.blueprint(bp)

➜  autodiscover cat blueprints/level1.py 
from sanic import Blueprint
from sanic.log import logger

level1 = Blueprint("level1")


@level1.after_server_start
def print_something(app, loop):
    logger.debug("something @ level1")%                                                                                                                                        
➜  autodiscover cat blueprints/one/two/level3.py 
from sanic import Blueprint
from sanic.log import logger

level3 = Blueprint("level3")


@level3.after_server_start
def print_something(app, loop):
    logger.debug("something @ level3")

➜  autodiscover cat listeners/something.py 
from sanic import Sanic
from sanic.log import logger

app = Sanic.get_app("auto")


@app.after_server_start
def print_something(app, loop):
    logger.debug("something")%                                                                                                                                                 
➜  autodiscover cat parent/child/nested.py 
from sanic import Blueprint
from sanic.log import logger

nested = Blueprint("nested")


@nested.after_server_start
def print_something(app, loop):
    logger.debug("something @ nested")%                                                                                                                                        
➜  autodiscover cat parent/child/__init__.py 
from sanic import Blueprint
from sanic.log import logger

bp = Blueprint("__init__")


@bp.after_server_start
def print_something(app, loop):
    logger.debug("something inside __init__.py")% 


thank you

I used the exact same code (copied from your post not the example) and it worked okay.

[2022-02-28 10:16:23 +0200] [1360806] [DEBUG] something
[2022-02-28 10:16:23 +0200] [1360806] [DEBUG] something @ level1
[2022-02-28 10:16:23 +0200] [1360806] [DEBUG] something inside __init__.py
[2022-02-28 10:16:23 +0200] [1360806] [DEBUG] something @ nested
[2022-02-28 10:16:23 +0200] [1360806] [DEBUG] something @ level3

How are you starting Sanic?

For me, I just did this:
image

But it still doesn’t work

➜  autodiscover sanic server:app --debug
Starting in v22.3, --debug will no longer automatically run the auto-reloader.
  Switch to --dev to continue using that functionality.
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/log.py:89: DeprecationWarning: [DEPRECATION v22.6] The register argument is deprecated and will stop working in v22.6. After v22.6 all apps will be added to the Sanic app registry.
  warn(version_info + message, DeprecationWarning)
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.9/bin/sanic", line 8, in <module>
    sys.exit(main())
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/__main__.py", line 12, in main
    cli.run()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/cli/app.py", line 75, in run
    app = self._get_app()
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/cli/app.py", line 127, in _get_app
    module = import_module(module_name)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/Users/bitmain/Desktop/Code/sanic/autodiscover/server.py", line 8, in <module>
    autodiscover(
  File "/Users/bitmain/Desktop/Code/sanic/autodiscover/utility.py", line 32, in autodiscover
    base = Path(module.__file__).parent
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pathlib.py", line 1082, in __new__
    self = cls._from_parts(args, init=False)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pathlib.py", line 707, in _from_parts
    drv, root, parts = self._parse_args(args)
  File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/pathlib.py", line 691, in _parse_args
    a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not NoneType

I changed my server, but the problem is still there

[[email protected] autodicover]# python3 --version
Python 3.9.0
[[email protected] autodicover]# python3  server.py
/usr/local/python3/lib/python3.9/site-packages/sanic/log.py:89: DeprecationWarning: [DEPRECATION v22.6] The register argument is deprecated and will stop working in v22.6. After v22.6 all apps will be added to the Sanic app registry.
  warn(version_info + message, DeprecationWarning)
Traceback (most recent call last):
  File "/root/sanic/autodicover/server.py", line 8, in <module>
    autodiscover(
  File "/root/sanic/autodicover/utility.py", line 32, in autodiscover
    base = Path(module.__file__).parent
  File "/usr/local/python3/lib/python3.9/pathlib.py", line 1071, in __new__
    self = cls._from_parts(args, init=False)
  File "/usr/local/python3/lib/python3.9/pathlib.py", line 696, in _from_parts
    drv, root, parts = self._parse_args(args)
  File "/usr/local/python3/lib/python3.9/pathlib.py", line 680, in _parse_args
    a = os.fspath(a)
TypeError: expected str, bytes or os.PathLike object, not NoneType
[[email protected] autodicover]# cat server.py
from sanic import Sanic
from sanic.response import empty

import blueprints
from utility import autodiscover

app = Sanic("auto", register=True)
autodiscover(
    app,
    blueprints,
    "parent.child",
    "listeners.something",
    recursive=True,
)

app.route("/")(lambda _: empty())

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8888, debug=True, auto_reload=True, access_log=True, workers=4)

When I comment out Blueprint, it works fine.

from sanic import Sanic
from sanic.response import empty
app = Sanic("auto", register=True)

import blueprints
from utility import autodiscover


autodiscover(
    app,
    # blueprints,
    "parent.child",
    "listeners.something",
    recursive=True,
)

app.route("/")(lambda _: empty())

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8888, debug=True, auto_reload=True, access_log=True, workers=4)

➜  autodiscover python3 server.py
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/log.py:89: DeprecationWarning: [DEPRECATION v22.6] The register argument is deprecated and will stop working in v22.6. After v22.6 all apps will be added to the Sanic app registry.
  warn(version_info + message, DeprecationWarning)
/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/sanic/log.py:89: DeprecationWarning: [DEPRECATION v22.6] The register argument is deprecated and will stop working in v22.6. After v22.6 all apps will be added to the Sanic app registry.
  warn(version_info + message, DeprecationWarning)
[2022-03-01 16:45:08 +0800] [1759] [INFO] 
  ┌────────────────────────────────────────────────────────────────────┐
  │                           Sanic v21.12.1                           │
  │                  Goin' Fast @ http://0.0.0.0:8888                  │
  ├───────────────────────┬────────────────────────────────────────────┤
  │                       │        mode: debug, w/ 4 workers           │
  │     ▄███ █████ ██     │      server: sanic                         │
  │    ██                 │      python: 3.9.10                        │
  │     ▀███████ ███▄     │    platform: macOS-10.16-x86_64-i386-64bit │
  │                 ██    │ auto-reload: enabled                       │
  │    ████ ████████▀     │    packages: sanic-routing==0.7.2          │
  │                       │                                            │
  │ Build Fast. Run Fast. │                                            │
  └───────────────────────┴────────────────────────────────────────────┘

[2022-03-01 16:45:08 +0800] [1765] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 16:45:08 +0800] [1763] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 16:45:08 +0800] [1762] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 16:45:08 +0800] [1764] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 16:45:08 +0800] [1765] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 16:45:08 +0800] [1762] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 16:45:08 +0800] [1763] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 16:45:08 +0800] [1765] [DEBUG] something
[2022-03-01 16:45:08 +0800] [1762] [DEBUG] something
[2022-03-01 16:45:08 +0800] [1764] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 16:45:08 +0800] [1763] [DEBUG] something
[2022-03-01 16:45:08 +0800] [1765] [DEBUG] something inside __init__.py
[2022-03-01 16:45:08 +0800] [1762] [DEBUG] something inside __init__.py
[2022-03-01 16:45:08 +0800] [1763] [DEBUG] something inside __init__.py
[2022-03-01 16:45:08 +0800] [1764] [DEBUG] something
[2022-03-01 16:45:08 +0800] [1762] [DEBUG] something @ nested
[2022-03-01 16:45:08 +0800] [1765] [DEBUG] something @ nested
[2022-03-01 16:45:08 +0800] [1763] [DEBUG] something @ nested
[2022-03-01 16:45:08 +0800] [1764] [DEBUG] something inside __init__.py
[2022-03-01 16:45:08 +0800] [1762] [INFO] Starting worker [1762]
[2022-03-01 16:45:08 +0800] [1764] [DEBUG] something @ nested
[2022-03-01 16:45:08 +0800] [1765] [INFO] Starting worker [1765]
[2022-03-01 16:45:08 +0800] [1763] [INFO] Starting worker [1763]
[2022-03-01 16:45:08 +0800] [1764] [INFO] Starting worker [1764]

If I add it as a string, then all blueprint will be introduced.

from sanic import Sanic
from sanic.response import empty
app = Sanic("auto", register=True)

import blueprints
from utility import autodiscover


autodiscover(
    app,
    # blueprints,
    "blueprints.level1",
    "parent.child",
    "listeners.something",
    recursive=True,
)

app.route("/")(lambda _: empty())

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8888, debug=True, auto_reload=True, access_log=True, workers=4)



[2022-03-01 17:06:24 +0800] [2167] [INFO] 
  ┌────────────────────────────────────────────────────────────────────┐
  │                           Sanic v21.12.1                           │
  │                  Goin' Fast @ http://0.0.0.0:8888                  │
  ├───────────────────────┬────────────────────────────────────────────┤
  │                       │        mode: debug, w/ 4 workers           │
  │     ▄███ █████ ██     │      server: sanic                         │
  │    ██                 │      python: 3.9.10                        │
  │     ▀███████ ███▄     │    platform: macOS-10.16-x86_64-i386-64bit │
  │                 ██    │ auto-reload: enabled                       │
  │    ████ ████████▀     │    packages: sanic-routing==0.7.2          │
  │                       │                                            │
  │ Build Fast. Run Fast. │                                            │
  └───────────────────────┴────────────────────────────────────────────┘

[2022-03-01 17:06:25 +0800] [2170] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] Dispatching signal: server.init.before
[2022-03-01 17:06:25 +0800] [2170] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 17:06:25 +0800] [2170] [DEBUG] something
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 17:06:25 +0800] [2170] [DEBUG] something inside __init__.py
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] something
[2022-03-01 17:06:25 +0800] [2170] [DEBUG] something @ level1
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] something
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] Dispatching signal: server.init.after
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] something inside __init__.py
[2022-03-01 17:06:25 +0800] [2170] [DEBUG] something @ level3
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] something inside __init__.py
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] something @ level1
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] something
[2022-03-01 17:06:25 +0800] [2170] [DEBUG] something @ nested
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] something @ level1
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] something @ level3
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] something inside __init__.py
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] something @ level3
[2022-03-01 17:06:25 +0800] [2172] [DEBUG] something @ nested
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] something @ level1
[2022-03-01 17:06:25 +0800] [2173] [DEBUG] something @ nested
[2022-03-01 17:06:25 +0800] [2170] [INFO] Starting worker [2170]
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] something @ level3
[2022-03-01 17:06:25 +0800] [2171] [DEBUG] something @ nested
[2022-03-01 17:06:25 +0800] [2172] [INFO] Starting worker [2172]
[2022-03-01 17:06:25 +0800] [2173] [INFO] Starting worker [2173]
[2022-03-01 17:06:25 +0800] [2171] [INFO] Starting worker [2171]

I was able to figure this out.

I can replicate this if the blueprints directory has no init.py in it.

If I add an __init__.py it works fine.

I believe this is because blueprints is (and is treated as) a module for doing the autodiscovery.

@ahopkins can you confirm?

thank you ,The problem was finally solved! :joy: :+1:

1 Like

Yes, that is correct. It would need to import it as a module. At some point we can think about different strategies and whether it is worth formalizing as a tool in sanic-ext with perhaps both file based and module based loading.

Hi guys,
I meet the same problem, but different error message.

File "H:\Xumeng\Code\Engineering\MiddleWares\API_Workers\utility.py", line 37, in autodiscover
    *_, name, __ = path.split("/")
ValueError: not enough values to unpack (expected at least 2, got 1)

I also add init.py to blueprints to let it work as a module

I found it is why, beacuse I run sanic on windows and the ‘/’ is not the speator of windows path, maybe we can update pathlib here?

I tried update the autodiscover code here


from glob import glob

from importlib import import_module, util

from inspect import getmembers

from pathlib import Path

from types import ModuleType

from typing import Union

import os

from sanic.blueprints import Blueprint

def autodiscover(

    app, *module_names: Union[str, ModuleType], recursive: bool = False

):

    mod = app.__module__

    blueprints = set()

    _imported = set()

    def _find_bps(module):

        nonlocal blueprints

        for _, member in getmembers(module):

            if isinstance(member, Blueprint):

                blueprints.add(member)

    for module in module_names:

        if isinstance(module, str):

            module = import_module(module, mod)

            _imported.add(module.__file__)

        _find_bps(module)

        if recursive:

            base = Path(module.__file__).parent

            for path in glob(f"{base}/**/*.py", recursive=True):

                if path not in _imported:

                    name = "module"

                    if "__init__" in path:

                        sep = "\\" if os.name == 'nt' else "/"

                       *_, name, __ = path.split(sep)

                    spec = util.spec_from_file_location(name, path)

                    specmod = util.module_from_spec(spec)

                    _imported.add(path)

                    spec.loader.exec_module(specmod)

                    _find_bps(specmod)

    for bp in blueprints:

        app.blueprint(bp)