├── .gitignore ├── .travis.yml ├── LICENSE ├── setup.py ├── README.md └── auto_all.py /.gitignore: -------------------------------------------------------------------------------- 1 | .coverage 2 | .idea/ 3 | __pycache__/ 4 | .mypy_cache/ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | language: python 3 | python: 4 | - '3.5' 5 | - '3.6' 6 | - '3.7' 7 | install: 8 | - pip install -U setuptools pip -r build-requirements.txt 9 | script: 10 | - pytest --doctest-modules --cov=auto_all auto_all.py 11 | deploy: 12 | provider: pypi 13 | skip_cleanup: true 14 | user: jongracecox 15 | password: 16 | secure: XtJHwJsH7TxU9a4MAcIhKDBu8r6xsSxSKbKIV5yGxwCvYEvjRCYk3oZp5f52brMg1SugCKeHXvIO/DhfVgy91KRlD6LsBkmVXDmyULV63ocfq3QfR84E+cz5bpzHdeg7+bNoLex1/y9rhqjPdHReTh44BbW+Jc1TJKidODJ4Ad8WaASOqKvd5a1JnUnjvg8I6mbU6II4P1yVT6vmEfkZt7mukW0TVmQd88ywVYI0JuP5Lo+rsNspC3HCnmwORZyPvZAT+OX0vTwH4VxROV035ZI+E6wo7UQAMjqpfTL1JQRzn7x3FfNE2GerOUd1zKoitm2FKsXfXkUvK7HWekpiznaoBIsCQj8Jomro3KS4YHsrYarT2khCztjMlFMnmEtCJses1fvHeRK24qbzGkLXkukgW+uWeyWSSVJR6ORvqmavweGHFCjs5siSoE5loE9yfM+nhWYG/WZNCR0n2xAHdVs7iXSYC6HpGwpNYNkotgi90aPL/Yw6crAvnnwdQGf3/nk0gIiTOF3g6t/kIj6dLZ4Go+r0FDHLOxWLRzl30WEEt5frSSeAjrOUBQisOuMqONflsZa1mPMnS4YxPJdpuAP3DqHVL5Lfv/NfnOv72Gh2TtvW6d4siVNq5EudnfaDFDvly2+yOVWIYYR32V4olYJZ7P3Kq57u7l8/ZDWUYqs= 17 | distributions: sdist bdist_wheel 18 | on: 19 | tags: true 20 | all_branches: true 21 | python: '3.7' 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 jongracecox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import re 4 | from setuptools import setup 5 | from m2r import parse_from_file 6 | import restructuredtext_lint 7 | 8 | # Parser README.md into reStructuredText format 9 | rst_readme = parse_from_file('README.md') 10 | 11 | # Validate the README, checking for errors 12 | errors = restructuredtext_lint.lint(rst_readme) 13 | 14 | # Raise an exception for any errors found 15 | if errors: 16 | print(rst_readme) 17 | raise ValueError('README.md contains errors: ', 18 | ', '.join([e.message for e in errors])) 19 | 20 | # Attempt to get version number from TravisCI environment variable 21 | version = os.environ.get('TRAVIS_TAG', default='0.0.0') 22 | 23 | # Remove leading 'v' 24 | version = re.sub('^v', '', version) 25 | 26 | setup( 27 | name='auto-all', 28 | description='Automatically manage __all__ variable in Python packages.', 29 | long_description=rst_readme, 30 | version=version, 31 | author='Jon Grace-Cox', 32 | author_email='jongracecox@gmail.com', 33 | py_modules=['auto_all'], 34 | setup_requires=['setuptools', 'wheel'], 35 | tests_require=['unittest'], 36 | install_requires=[], 37 | data_files=[], 38 | options={ 39 | 'bdist_wheel': {'universal': True} 40 | }, 41 | url='https://github.com/jongracecox/auto-all', 42 | classifiers=[ 43 | 'License :: OSI Approved :: MIT License' 44 | ] 45 | ) 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # auto-all 2 | 3 | Automatically manage the `__all__` variable in Python modules. 4 | 5 | [![pypi package](https://badge.fury.io/py/auto-all.svg)](https://pypi.org/project/auto-all) 6 | [![build status](https://api.travis-ci.com/jongracecox/auto-all.svg?branch=master)](https://travis-ci.com/jongracecox/auto-all) 7 | [![downloads](https://img.shields.io/pypi/dm/auto-all.svg)](https://pypistats.org/packages/auto-all) 8 | [![GitHub last commit](https://img.shields.io/github/last-commit/jongracecox/auto-all.svg)](https://github.com/jongracecox/auto-all/commits/master) 9 | [![GitHub](https://img.shields.io/github/license/jongracecox/auto-all.svg)](https://github.com/jongracecox/auto-all/blob/master/LICENSE) 10 | [![GitHub stars](https://img.shields.io/github/stars/jongracecox/auto-all.svg?style=social)](https://github.com/jongracecox/auto-all/stargazers) 11 | 12 | [![buymeacoffee](https://camo.githubusercontent.com/c3f856bacd5b09669157ed4774f80fb9d8622dd45ce8fdf2990d3552db99bd27/68747470733a2f2f7777772e6275796d6561636f666665652e636f6d2f6173736574732f696d672f637573746f6d5f696d616765732f6f72616e67655f696d672e706e67)](https://www.buymeacoffee.com/jongracecox) 13 | 14 | ## Overview 15 | 16 | `auto_all` can be used for controlling what is made available 17 | for import from a Python module. 18 | 19 | Advantages: 20 | 21 | * Easily populate the `__all__` variable in modules. 22 | * Easily exclude imported objects 23 | * Clearly differentiate between internal and external facing objects. 24 | * Use simple, intuitive code. 25 | * Never worry about forgetting to add new objects to `__all__`. 26 | * Help Python IDE's differentiate between internal and external facing objects. 27 | 28 | ## Installation 29 | 30 | ```bash 31 | pip install auto-all 32 | ``` 33 | 34 | ## Usage 35 | 36 | There are two main approaches: 37 | 38 | 1) Use `start_all` and `end_all` to wrap all public functions and 39 | variables. 40 | 2) Use the `@public` decorator to identify publicly facing functions. 41 | 42 | ### start_all/end_all approach 43 | 44 | First, import the auto_all functions into your module. 45 | 46 | ```python 47 | from auto_all import start_all, end_all 48 | ``` 49 | 50 | If your module has external dependencies then these can be imported 51 | and the imported objects can be hidden. In this example we will import 52 | pathlib.Path and show that it doesn't appear on the `__all__` list. 53 | We're not actually going to use this import, it's just for illustration. 54 | 55 | ```python 56 | from pathlib import Path 57 | ``` 58 | 59 | Now we can define some internal functions that we want to keep private. 60 | We can also do this using underscore prefixes, but `auto_all` gives us a 61 | little more granular control. 62 | 63 | ```python 64 | def a_private_function(): 65 | print("This is a private function.") 66 | ``` 67 | 68 | Now we are ready to start defining public functions, so we use 69 | `start_all()`. 70 | 71 | ```python 72 | start_all() 73 | ``` 74 | 75 | Now we can define our public functions. 76 | 77 | ```python 78 | def a_public_function(): 79 | print("This is a public function.") 80 | ``` 81 | 82 | Finally we use `end_all()` to finish defining public functions and 83 | create the `__all__` variable. 84 | 85 | ```python 86 | end_all() 87 | ``` 88 | 89 | When we look at the `__all__` variable we can see only the public 90 | facing objects are listed. 91 | 92 | ``` 93 | >>> print(__all__) 94 | ['a_public_function'] 95 | ``` 96 | 97 | Putting this all together, your module should look something like this: 98 | 99 | ```python 100 | from auto_all import start_all, end_all 101 | 102 | from pathlib import Path 103 | 104 | def a_private_function(): 105 | print("This is a private function.") 106 | 107 | start_all() 108 | 109 | def a_public_function(): 110 | print("This is a public function.") 111 | 112 | end_all() 113 | ``` 114 | 115 | It is possible to pass the globals dict to the `start_all` and 116 | `end_all` function calls. This is not typically necessary, and is 117 | only included for backward compatibility. 118 | 119 | ```python 120 | start_all(globals()) 121 | 122 | def another_public_function(): 123 | pass 124 | 125 | end_all(globals()) 126 | 127 | def a_private_function(): 128 | pass 129 | 130 | print(__all__) 131 | ``` 132 | 133 | ### `@public` decorator approach 134 | 135 | The second approach is to use the `@public` decorator. Note that this 136 | approach is only suitable for functions, and will not work for declaring 137 | classes or variables as public. 138 | 139 | First, import the decorator: 140 | 141 | ```python 142 | from auto_all import public 143 | ``` 144 | 145 | We can define any private functions without any decorator: 146 | 147 | ```python 148 | def a_private_function(): 149 | pass 150 | ``` 151 | 152 | We can define public functions by decorating with the `@public` 153 | decorator: 154 | 155 | ```python 156 | @public 157 | def a_public_function(): 158 | pass 159 | ``` 160 | 161 | The `__all__` variable will only include functions that have been 162 | declared as public: 163 | 164 | ``` 165 | >>> print(__all__) 166 | ['a_public_function'] 167 | ``` 168 | 169 | Combining the two approaches 170 | ============================ 171 | 172 | In the event that you need to declare variables and classes as public, and 173 | also want to make use of the `@public` decorator for functions you can 174 | combine both methods. 175 | 176 | Private variables can be defined outside the start/end block: 177 | 178 | ```python 179 | PRIVATE_VARIABLE = "I am private" 180 | ``` 181 | 182 | Public items can be defined between the `start_all()` and `end_all()` 183 | function calls: 184 | 185 | ```python 186 | start_all() 187 | PUBLIC_VARIABLE = "I am public" 188 | class PublicClass: 189 | pass 190 | end_all() 191 | ``` 192 | 193 | Private functions can be defined undecorated outside the start/end block: 194 | 195 | ```python 196 | def private_function(): 197 | pass 198 | ``` 199 | 200 | Public functions can be decorated with the `@public` decorator: 201 | 202 | ```python 203 | @public 204 | def public_function(): 205 | pass 206 | ``` 207 | 208 | The `__all__` variable will include any object declared between the 209 | `start_all` and `end_all` calls, and any function decorated with the 210 | `@public` decorator: 211 | 212 | ``` 213 | >>> print(__all__) 214 | ['PUBLIC_VARIABLE', 'PublicClass', 'public_function'] 215 | ``` 216 | -------------------------------------------------------------------------------- /auto_all.py: -------------------------------------------------------------------------------- 1 | """ 2 | Auto-All package 3 | 4 | auto_all can be used for controlling what is made available 5 | for import from a package. 6 | 7 | There are two main approaches: 8 | 9 | 1) Use ``start_all`` and ``end_all`` to wrap all public 10 | functions and variables. 11 | 2) Use the ``@public`` decorator to identify publicly 12 | facing functions. 13 | 14 | start_all/end_all Approach 15 | ========================== 16 | 17 | First, import the auto_all functions. 18 | 19 | >>> from auto_all import start_all, end_all 20 | 21 | If your package has external dependencies then these can be imported 22 | and the imported objects can be hidden. In this case we will import 23 | pathlib.Path and show that it doesn't appear on the __all__ list. 24 | 25 | >>> from pathlib import Path 26 | 27 | Now we can define some internal functions that we want to keep private. 28 | We can also do this using underscore prefixes, but auto_all gives us a 29 | little more control. 30 | 31 | >>> def a_private_function(): 32 | ... print("This is a private function.") 33 | 34 | Now we are ready to start defining public functions, so we use 35 | ``start_all()``. 36 | 37 | >>> start_all() 38 | 39 | Now we can define our public functions. 40 | 41 | >>> def a_public_function(): 42 | ... print("This is a public function.") 43 | 44 | Finally we use ``end_all()`` to finish defining public functions and 45 | create the ``__all__`` variable. 46 | 47 | >>> end_all() 48 | 49 | When we look at the ``__all__`` variable we can see only the public 50 | facing function is listed. 51 | 52 | >>> print(__all__) 53 | ['a_public_function'] 54 | 55 | It is possible to pass the globals dict to the ``start_all`` and 56 | ``end_all`` function calls. This is not typically necessary, and is 57 | only included for backward compatibility. 58 | 59 | >>> del __all__ # Delete __all__ for demo purposes 60 | 61 | >>> start_all(globals()) 62 | 63 | >>> def another_public_function(): 64 | ... pass 65 | 66 | >>> end_all(globals()) 67 | 68 | >>> def a_private_function(): 69 | ... pass 70 | 71 | >>> print(__all__) 72 | ['another_public_function'] 73 | 74 | @public decorator approach 75 | ========================== 76 | 77 | The second approach is to use the ``@public`` decorator. Note that this 78 | approach is only suitable for functions, and will not work for declaring 79 | classes or variables as public. 80 | 81 | First, import the decorator: 82 | 83 | >>> del __all__ # Delete __all__ for demo purposes 84 | >>> from auto_all import public 85 | 86 | We can define any private functions without any decorator: 87 | 88 | >>> def a_private_function(): 89 | ... pass 90 | 91 | We can define public functions by decorating with the ``@public`` 92 | decorator: 93 | 94 | >>> @public 95 | ... def a_public_function(): 96 | ... pass 97 | 98 | The ``__all__`` variable will only include functions that have been 99 | declared as public: 100 | 101 | >>> print(__all__) 102 | ['a_public_function'] 103 | 104 | Combining the two approaches 105 | ============================ 106 | 107 | In the event that you need to declare variables and classes as public, and 108 | also want to make use of the ``@public`` decorator for functions you can 109 | combine both methods. 110 | 111 | >>> del __all__ # Delete __all__ for demo purposes 112 | 113 | Private variables can be defined outside the start/end block: 114 | 115 | >>> PRIVATE_VARIABLE = "I am private" 116 | 117 | Public items can be defined between the ``start_all()`` and ``end_all()`` 118 | function calls: 119 | 120 | >>> start_all() 121 | >>> PUBLIC_VARIABLE = "I am public" 122 | >>> class PublicClass: 123 | ... pass 124 | >>> end_all() 125 | 126 | Private functions can be defined undecorated outside the start/end block: 127 | 128 | >>> def private_function(): 129 | ... pass 130 | 131 | Public functions can be decorated with the ``@public`` decorator: 132 | 133 | >>> @public 134 | ... def public_function(): 135 | ... pass 136 | 137 | The ``__all__`` variable will include any object declared between the 138 | ``start_all`` and ``end_all`` calls, and any function decorated with the 139 | ``@public`` decorator: 140 | 141 | >>> print(sorted(__all__)) 142 | ['PUBLIC_VARIABLE', 'PublicClass', 'public_function'] 143 | 144 | """ 145 | import inspect 146 | from typing import Callable 147 | from typing import Dict, Optional 148 | _GLOBAL_VAR_NAME = '_do_not_include_all' 149 | 150 | 151 | def _get_globals(): 152 | """Get global dict from stack.""" 153 | calling_module = inspect.stack()[2] 154 | local_stack = calling_module[0] 155 | return local_stack.f_globals 156 | 157 | 158 | def start_all(globs: Optional[Dict] = None): 159 | """Start defining externally accessible objects. 160 | 161 | Call ``start_all(globals())`` when you want to start defining objects 162 | in your module that you want to be accessible from outside the module. 163 | 164 | Args: 165 | globs(dict, optional): Pass the globals dictionary to the function 166 | using ``globals()``. 167 | """ 168 | if not globs: 169 | globs = _get_globals() 170 | 171 | globs[_GLOBAL_VAR_NAME] = list(globs.keys()) + [_GLOBAL_VAR_NAME] 172 | 173 | 174 | def end_all(globs: Optional[Dict] = None): 175 | """Finish defining externally accessible objects. 176 | 177 | Call ``end_all(globals())`` when you have finished defining objects 178 | in your module that you want to be accessible from outside the module. 179 | After this function has been run the ``__all__`` module variable will 180 | be updated with the names of all objects that were created between the 181 | ``start_all`` and ``end_all`` funciton calls. 182 | 183 | Args: 184 | globs(dict, optional): Pass the globals dictionary to the function 185 | using ``globals()``. 186 | """ 187 | if not globs: 188 | globs = _get_globals() 189 | 190 | globs['__all__'] = list( 191 | set(list(globs.keys())) - set(globs[_GLOBAL_VAR_NAME]) 192 | ) 193 | 194 | 195 | def public(func: Callable): 196 | """Decorator that adds a function to the modules __all__ list.""" 197 | 198 | local_stack = inspect.stack()[1][0] 199 | 200 | global_vars = local_stack.f_globals 201 | 202 | if '__all__' not in global_vars: 203 | global_vars['__all__'] = [] 204 | 205 | all_var = global_vars['__all__'] 206 | 207 | all_var.append(func.__name__) 208 | 209 | return func 210 | 211 | __all__ = ['start_all', 'end_all', 'public'] 212 | --------------------------------------------------------------------------------