Giter Club home page Giter Club logo

python-module-resources's Introduction

python-module-resources

Import non-python files in a project directory as python namedtuple objects.

If you've ever worked in node, you might be familiar with a language feature which allows you to pull in a json file as an imported module.

import { dataStuff } from 'myProjectResources/jsonFiles'

dataStuff.contents === "A rich javascript object"

With module_resources, you can achieve something similar in python.

from my_project_resources.json_files import data_stuff

data_stuff.contents == 'A python namedtuple instance'

Getting Started

To use this in your own project, install with pip.

pip install module-resources
# supports yaml files too
pip install module-resources[yaml]

Create a module location in your project where your desired importable resource files will live. I will use this project as an example.

mkdir module_resources/examples/json/
touch module_resources/examples/json/__init__.py

Your module's __init__.py file does the heavy lifting.

from module_resources import JsonModuleResource
__all__ = JsonModuleResource(__name__, __file__).intercept_imports()

From there, move the resources you'd like to import as python objects into that directory.

tree ./module_resources/examples/json/
module_resources/examples/json/
├── __init__.py
└── logging_config.json

0 directories, 2 files

You can now import some logger object's configuration json as a python namedtuple. It has a few interesting properties about it that will hightlight some caveats about this tool.

> python
Python 3.7.2 (default, Jan 14 2019, 16:30:40)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from module_resources.examples.json import logging_config
>>> logging_config.formatters.simple
json(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')

You may refer to any valid namedtuple property by using dot-attribute notation.

>>> logger.formatters.simple.format
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'

Yaml configuration files work as well, with the module_resources.YamlResourceModule class.

>>> from module_resources.examples.yaml import logging_config as yaml_logging_config
>>> yaml_logging_config.formatters
yaml(simple=yaml(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

To turn this object into a dictionary, pass it to dict().

>>> import logging.config
>>> logging.config.dictConfig(dict(yaml_logging_config))
>>> logging.getLogger('test').info('testing')
2019-07-23 11:43:57,296 - test - INFO - testing

Caveats

There are some caveats to be aware of.

Valid vs. invalid namedtuple field names

>>> from module_resources.examples.json import logging_config
>>> logging_config.loggers.__main__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'ImportableFallbackDict' object has no attribute '__main__'

In namedtuple fields, __main__ is not a legal name. It gets exposed as a dictionary type, ImportableFallbackDict.

>>> logging_config.loggers['__main__'].level
'INFO'

It does store valid property names in that dictionary as namedtuples, however.

Exporting only a portion of a file

You may export from the top level namespace of a resource.

>>> from module_resources.examples.json.logging_cofig import formatters
>>> formatters.simple.format
'INFO'

You can't export arbitrarily deep from a json or yaml file. Only one level deep is supported.

Type hints for module resources

Using this tool is going to make pylint unhappy.

from module_resources.examples.json import logging_config

import logging.config
logging.config.dictConfig(dict(logger))

The above code is going to raise complaints from pylint.

pylint: Unable to import 'module_resources.example.json.logging_config' ("import-error") [E0401]

This tool leverages highly dynamic features of the python language to accomplish its work, and as such properties and types won't be available until runtime. You will need to include exceptions for these objects in your codebase if you want to maintain a high lint score while using this tool.

Development

A Linux or Mac environment is assumed for development, running python version 3.

Prerequisites

make preqres

This script will help ensure you have the necessary tools for developing against the project locally.

Installing

make virtualenv
. .venv/bin/activate

Run the tests after installing the dependencies.

Running the tests

make tests

And coding style tests

make lint
make mypy
make bandit

Deployment

Open a pull request against the master branch. Travis-CI will publish a preview version after all tests pass. You can install this preview version of your branch build from test.pypi.org.

pip install -i https://test.pypi.org/simple/ module-resources==0.0.${TRAVIS_BUILD_NUMBER}

Note that if you open a pull request from a fork, this step won't run.

To publish a new official version, tag a commit and push it up to the master branch.

git checkout master
git pull origin master
# examples of preparing a new tag for release
make tag-patch # also accepts: tag-minor, tag-major
git push origin --tags

Note that you must create and push tags from the master branch only. Tags found in pull requests won't do anything.

Contributing

Small pull requests are fine to submit directly as pull requests. Larger changes should first be submitted as issues and mapped out before starting work on the proposal.

Please read CONTRIBUTING.md for details on the code of conduct, and the process for submitting pull requests.

Versioning

This project uses SemVer for versioning. For the versions available, see the tags on this repository.

Authors

See the list of contributors who have participated in this project.

This list can be updated with make contributors.

License

This project is licensed under the MIT License.

Acknowledgments

python-module-resources's People

Contributors

captain-kark avatar

Watchers

 avatar  avatar

python-module-resources's Issues

Write to resource

A resource object should know how to write itself back to the filesystem in the same location and format it was in when it was imported.

Test needed: dict() conversion should never update original object

This came up during manual testing. I fixed it, but a regression test needs to be written.

See references to clean_value in module_resources.py for where I handled this issue in the first place.

Running dict() had a side effect of altering the namedtuple as it iterated through it.

Iterating over module resources is awkward

The __iter__ method is currently designed to help dict() work easily, but it makes for-loops deal with dicts, making things clumsy when working with loops.

Find a way to continue using __iter__ as dicts for calls to dict(), while avoiding this type of interaction in loops.

from some_module_resource import module_json_resource

dict(module_json_resource)
# {"first": {"namedtuple": True}, "second": {"namedtuple": True}}

for resource_key, dict_value in module_json_resource:
    namedtuple_value = getattr(module_json_resource, resource_key)
    print(hasattr(namedtuple_value.second))

Support for directories in a resource module

Add a new directory, module_resources/examples/{json,yaml}/subdirectory to the examples, and ensure that imports are handled orderly.

Account for matching names between a file in the module's top-level directory, and a subdirectory within the module. Use from to distinguish these submodule imports versus named documents in the top-level.

Tests needed: ensure that intercepts from directories other than the current module location works

You should be able to import a module and have the contents of the imported object sourced from someplace like /var/config, /tmp/whatever/, etc.

Imports would still be requesting the same module that the associated __init__.py file is located in. It would just affect where the source json/yaml files are searched for.

This might already be the default behavior. If it isn't, it should support this.

Readme typo

Yaml configuration files work as well, with the module_resources.YamlResourceModule class.

Should be module_resources.YamlModuleResource.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.