4

I practiced using text data as a module as below. And it works very well.

# function.py

def function(): print('hello')

import bpy
f = bpy.data.texts['function.py'].as_module()

f.function()
>> 'hello'

But I want to use the from import statement. But I couldn't find a way.

Can text data be recalled to the from import statement as a module and used?

J. SungHoon
  • 2,196
  • 11
  • 36

1 Answers1

12

Add to sys.modules

Among other ways to make fake modules, can simply add it to sys.modules

>>> import sys
>>> sys.modules["function"] = bpy.data.texts['function.py'].as_module()
>>> from function import function
>>> function()
hello

Have a good link re fake modules and namespaces, will look for it.

https://stackoverflow.com/questions/5122465/can-i-fake-a-package-or-at-least-a-module-in-python-for-testing-purposes/27476659#27476659

Will add this link here too, https://dev.to/dangerontheranger/dependency-injection-with-import-hooks-in-python-3-5hap in can make a fake namespace that can be imported as if a regular module.

An example using above (stripped of comments to make shorter) making the function method appear to come from module my_addon.utils

import importlib.abc
import importlib.machinery
import sys
import types

class DependencyInjectorFinder(importlib.abc.MetaPathFinder): def init(self, loader): self._loader = loader def find_spec(self, fullname, path, target=None):

    if self._loader.provides(fullname):
        return self._gen_spec(fullname)
def _gen_spec(self, fullname):
    spec = importlib.machinery.ModuleSpec(fullname, self._loader)
    return spec


class DependencyInjectorLoader(importlib.abc.Loader): _COMMON_PREFIX = "my_addon." def init(self): self._services = {} self._dummy_module = types.ModuleType(self._COMMON_PREFIX[:-1])

    self._dummy_module.__path__ = []
def provide(self, service_name, module):
    self._services[service_name] = module
def provides(self, fullname):
    if self._truncate_name(fullname) in self._services:
        return True
    else:
        return self._COMMON_PREFIX.startswith(fullname)
def create_module(self, spec):

    service_name = self._truncate_name(spec.name)
    if service_name not in self._services:
        return self._dummy_module
    module = self._services[service_name]
    return module
def exec_module(self, module):

    pass
def _truncate_name(self, fullname):
    return fullname[len(self._COMMON_PREFIX):] 


class DependencyInjector:

def __init__(self):
    self._loader = DependencyInjectorLoader()
    self._finder = DependencyInjectorFinder(self._loader)
def install(self):
    sys.meta_path.append(self._finder)
def provide(self, service_name, module):
    self._loader.provide(service_name, module)


if name == "main": import bpy injector = DependencyInjector()

injector.provide("utils", bpy.data.texts["functions.py"].as_module())
injector.install()

import my_addon.utils as utils
utils.function()

output

hello
batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • 1
    This might be the most convoluted Hello World I've ever seen :) – Gorgious Feb 23 '21 at 07:17
  • Yep. Ummed and ahhed whether to include. 2nd part prob not required here. Found it handy as a use case for from my_addon.libs import matplotlib to inject some dummy for the case of the real one not installed. In this case OP can develop a script in blend file text editor to look like from my_addon.utils import function Thanks for the feedback, it's appreciated. – batFINGER Feb 23 '21 at 08:27
  • In any case I really liked it, I don't understand most of it but it seems really powerful and I'll gladly dive deep into it :) – Gorgious Feb 23 '21 at 09:05
  • Thank you! This solved it. – J.Nada Aug 10 '21 at 21:47