Browse Source

Finalize prototype

master
Brett Langdon 7 years ago
parent
commit
0e18784456
No known key found for this signature in database GPG Key ID: B664881177781B04
7 changed files with 222 additions and 60 deletions
  1. +4
    -0
      .gitignore
  2. +10
    -0
      CHANGELOG
  3. +21
    -0
      LICENSE
  4. +1
    -0
      MANIFEST.in
  5. +94
    -0
      README.md
  6. +37
    -0
      setup.py
  7. +55
    -60
      virtualmod.py

+ 4
- 0
.gitignore View File

@ -0,0 +1,4 @@
*.egg-info
dist
build
.py[co]

+ 10
- 0
CHANGELOG View File

@ -0,0 +1,10 @@
virtualmod Changelog
===================
Version 1.0.0
-------------
:Released: 09-25-2018
:Changes:
Initial release

+ 21
- 0
LICENSE View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Brett Langdon
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.

+ 1
- 0
MANIFEST.in View File

@ -0,0 +1 @@
include CHANGELOG LICENSE README.rst virtualmod.py

+ 94
- 0
README.md View File

@ -0,0 +1,94 @@
virtualmod
==========
Python package for creating and importing virtual modules.
## Install
```bash
pip install virtualmod
```
## Examples
### Module object
Manually creating and registering a module with `virtualmod`.
```python
import virtualmod
# Create a new empty virtual module
module = virtualmod.create_module('custom_module')
# Add attribute to module
module.key = 'value'
# Use decorator to add a function to the module
# NOTE: You can use `add_to_module(module, name='new_name')` to override the module attribute name
@virtualmod.add_to_module(module)
def module_function(name):
print('Hello', name)
# Use decorator to add a class to the module
@virtualmod.add_to_module(module)
class ModuleClass:
pass
# Import and use our virtual module
import custom_module
print('Key:', custom_module.key)
custom_module.module_function('virtualmod')
print(custom_module.ModuleClass())
```
### Class definition
`virtualmod` also comes with the ability to use class definitions to define virtual modules.
```python
import virtualmod
# Use class definition to define our virtual module "custom_module"
class CustomModule(virtualmod.VirtualModule):
# Define the module's name (would be "CustomModule" otherwise)
__module_name__ = 'custom_module'
# Add an attribute
key = 'value'
# Add a function
# NOTE: There is no `cls` or `self`
def module_function(name):
print('Hello', name)
# Add a class to the module
class ModuleClass:
pass
# Import and use our virtual module
import custom_module
print('Key:', custom_module.key)
custom_module.module_function('virtualmod')
print(custom_module.ModuleClass())
```
### Override an existing module
`virtualmod`'s module finder is registered before the standard builtin finders.
This means if you register a module under a name of an existing module yours would be found and loaded first
```python
import virtualmod
# Create a virtual module under the name "socket"
my_socket = virtualmod.create_module('socket')
# Import socket module
import socket
# Test if the loaded socket module is the one we created
print(socket is my_socket)
```

+ 37
- 0
setup.py View File

@ -0,0 +1,37 @@
"""
virtualmod
==========
"""
from setuptools import setup
def get_long_description():
with open('README.md') as f:
rv = f.read()
return rv
setup(
name='virtualmod',
version='1.0.0',
url='https://github.com/brettlangdon/virtualmod',
license='MIT',
author='Brett Langdon',
author_email='me@brett.is',
description='Python package for creating and importing virtual modules.',
long_description=get_long_description(),
long_description_content_type='text/markdown',
py_modules=['virtualmod'],
zip_safe=False,
include_package_data=True,
platforms='any',
install_requires=[],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 3',
'Programming Language :: Python',
]
)

+ 55
- 60
virtualmod.py View File

@ -2,81 +2,47 @@ import importlib
from importlib.abc import Loader, MetaPathFinder
import sys
__all__ = [
'MetaVirtualModule',
'VirtualModule',
'add_to_module',
'create_module',
]
# Our virtual module registry
registry = dict()
# Built in module class
module_cls = type(sys)
# Built in module spec class
spec_cls = type(sys.__spec__)
class VirtualModule:
__slots__ = ['name', 'module', 'spec']
def __init__(self, name):
self.name = name
self.module = module_cls(name)
self.spec = spec_cls(name=name, loader=VirtualModuleLoader)
setattr(self.module, '__spec__', self.spec)
def set_attribute(self, name, value):
setattr(self.module, name, value)
def delete_attribute(self, name):
delattr(self.module, name)
def create_module(name):
module = VirtualModule(name)
registry[name] = module
def create_module(module_name):
"""Function for create a new empty virtual module and register it"""
module = module_cls(module_name)
setattr(module, '__spec__', spec_cls(name=module_name, loader=VirtualModuleLoader))
registry[module_name] = module
return module
def copy_module(module):
name = module.__name__
if hasattr(module, '__spec__'):
name = module.__spec__.name
virt_mod = VirtualModule(name)
for key, value in module.__dict__.items():
if key in ('__spec__', '__name__', '__loader__', '__package__'):
continue
virt_mod.set_attribute(key, value)
registry[name] = virt_mod
importlib.reload(module)
return virt_mod
def as_module(cls_or_name):
if isinstance(cls_or_name, str):
cls = None
name = cls_or_name
elif isinstance(cls_or_name, type):
cls = cls_or_name
name = getattr(cls, '__module_name__', cls.__name__)
else:
raise ValueError('Expected as_module to be passed a string or a class type')
def wrapper(cls):
module = create_module(name)
for key, value in cls.__dict__.items():
if key.startswith('__') and key.endswith('__'):
continue
module.set_attribute(key, value)
return module
if cls is None:
return wrapper
return wrapper(cls)
def add_to_module(module, name=None):
"""Decorator to register a function or class to a module"""
def wrapper(value):
key = name or getattr(value, '__name__', None)
if key:
setattr(module, key, value)
return value
return wrapper
class VirtualModuleLoader(Loader):
"""Module loader class used for pulling virtual modules from our registry"""
def create_module(spec):
if spec.name not in registry:
return None
return registry[spec.name].module
return registry[spec.name]
def exec_module(module):
module_name = module.__name__
@ -87,10 +53,39 @@ class VirtualModuleLoader(Loader):
class VirtualModuleFinder(MetaPathFinder):
"""Module finder to register with sys.meta_path for finding module specs from our registry"""
def find_spec(fullname, path, target=None):
if fullname in registry:
return registry[fullname].spec
return registry[fullname].__spec__
return None
class MetaVirtualModule(type):
"""Metaclass used for automatically creating and registering VirtualModule class definitions"""
def __init__(cls, name, bases, attrs):
# Initialize the class
super(MetaVirtualModule, cls).__init__(name, bases, attrs)
# Do not register our base class
if name == 'VirtualModule':
return
module_name = getattr(cls, '__module_name__', cls.__name__) or name
# DEV: `create_module` will registry this module for us
module = create_module(module_name)
# Copy over class attributes
for key, value in attrs.items():
if key in ('__name__', '__module_name__', '__module__', '__qualname__'):
continue
setattr(module, key, value)
class VirtualModule(metaclass=MetaVirtualModule):
"""Base virtual module class for creating modules from class definitions"""
pass
# Push our virtual module finder at the beginning of the sys.meta_path
# DEV: Push in first so we always look for virtual modules first
sys.meta_path.insert(0, VirtualModuleFinder)

Loading…
Cancel
Save