Adding all project files
This commit is contained in:
parent
6c9e127bdc
commit
cd4316ad0f
42289 changed files with 8009643 additions and 0 deletions
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2014-2016 Nathan West
|
||||
#
|
||||
# This file is part of autocommand.
|
||||
#
|
||||
# autocommand is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# autocommand is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with autocommand. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# flake8 flags all these imports as unused, hence the NOQAs everywhere.
|
||||
|
||||
from .automain import automain # NOQA
|
||||
from .autoparse import autoparse, smart_open # NOQA
|
||||
from .autocommand import autocommand # NOQA
|
||||
|
||||
try:
|
||||
from .autoasync import autoasync # NOQA
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,142 @@
|
|||
# Copyright 2014-2015 Nathan West
|
||||
#
|
||||
# This file is part of autocommand.
|
||||
#
|
||||
# autocommand is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# autocommand is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with autocommand. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from asyncio import get_event_loop, iscoroutine
|
||||
from functools import wraps
|
||||
from inspect import signature
|
||||
|
||||
|
||||
async def _run_forever_coro(coro, args, kwargs, loop):
|
||||
'''
|
||||
This helper function launches an async main function that was tagged with
|
||||
forever=True. There are two possibilities:
|
||||
|
||||
- The function is a normal function, which handles initializing the event
|
||||
loop, which is then run forever
|
||||
- The function is a coroutine, which needs to be scheduled in the event
|
||||
loop, which is then run forever
|
||||
- There is also the possibility that the function is a normal function
|
||||
wrapping a coroutine function
|
||||
|
||||
The function is therefore called unconditionally and scheduled in the event
|
||||
loop if the return value is a coroutine object.
|
||||
|
||||
The reason this is a separate function is to make absolutely sure that all
|
||||
the objects created are garbage collected after all is said and done; we
|
||||
do this to ensure that any exceptions raised in the tasks are collected
|
||||
ASAP.
|
||||
'''
|
||||
|
||||
# Personal note: I consider this an antipattern, as it relies on the use of
|
||||
# unowned resources. The setup function dumps some stuff into the event
|
||||
# loop where it just whirls in the ether without a well defined owner or
|
||||
# lifetime. For this reason, there's a good chance I'll remove the
|
||||
# forever=True feature from autoasync at some point in the future.
|
||||
thing = coro(*args, **kwargs)
|
||||
if iscoroutine(thing):
|
||||
await thing
|
||||
|
||||
|
||||
def autoasync(coro=None, *, loop=None, forever=False, pass_loop=False):
|
||||
'''
|
||||
Convert an asyncio coroutine into a function which, when called, is
|
||||
evaluted in an event loop, and the return value returned. This is intented
|
||||
to make it easy to write entry points into asyncio coroutines, which
|
||||
otherwise need to be explictly evaluted with an event loop's
|
||||
run_until_complete.
|
||||
|
||||
If `loop` is given, it is used as the event loop to run the coro in. If it
|
||||
is None (the default), the loop is retreived using asyncio.get_event_loop.
|
||||
This call is defered until the decorated function is called, so that
|
||||
callers can install custom event loops or event loop policies after
|
||||
@autoasync is applied.
|
||||
|
||||
If `forever` is True, the loop is run forever after the decorated coroutine
|
||||
is finished. Use this for servers created with asyncio.start_server and the
|
||||
like.
|
||||
|
||||
If `pass_loop` is True, the event loop object is passed into the coroutine
|
||||
as the `loop` kwarg when the wrapper function is called. In this case, the
|
||||
wrapper function's __signature__ is updated to remove this parameter, so
|
||||
that autoparse can still be used on it without generating a parameter for
|
||||
`loop`.
|
||||
|
||||
This coroutine can be called with ( @autoasync(...) ) or without
|
||||
( @autoasync ) arguments.
|
||||
|
||||
Examples:
|
||||
|
||||
@autoasync
|
||||
def get_file(host, port):
|
||||
reader, writer = yield from asyncio.open_connection(host, port)
|
||||
data = reader.read()
|
||||
sys.stdout.write(data.decode())
|
||||
|
||||
get_file(host, port)
|
||||
|
||||
@autoasync(forever=True, pass_loop=True)
|
||||
def server(host, port, loop):
|
||||
yield_from loop.create_server(Proto, host, port)
|
||||
|
||||
server('localhost', 8899)
|
||||
|
||||
'''
|
||||
if coro is None:
|
||||
return lambda c: autoasync(
|
||||
c, loop=loop,
|
||||
forever=forever,
|
||||
pass_loop=pass_loop)
|
||||
|
||||
# The old and new signatures are required to correctly bind the loop
|
||||
# parameter in 100% of cases, even if it's a positional parameter.
|
||||
# NOTE: A future release will probably require the loop parameter to be
|
||||
# a kwonly parameter.
|
||||
if pass_loop:
|
||||
old_sig = signature(coro)
|
||||
new_sig = old_sig.replace(parameters=(
|
||||
param for name, param in old_sig.parameters.items()
|
||||
if name != "loop"))
|
||||
|
||||
@wraps(coro)
|
||||
def autoasync_wrapper(*args, **kwargs):
|
||||
# Defer the call to get_event_loop so that, if a custom policy is
|
||||
# installed after the autoasync decorator, it is respected at call time
|
||||
local_loop = get_event_loop() if loop is None else loop
|
||||
|
||||
# Inject the 'loop' argument. We have to use this signature binding to
|
||||
# ensure it's injected in the correct place (positional, keyword, etc)
|
||||
if pass_loop:
|
||||
bound_args = old_sig.bind_partial()
|
||||
bound_args.arguments.update(
|
||||
loop=local_loop,
|
||||
**new_sig.bind(*args, **kwargs).arguments)
|
||||
args, kwargs = bound_args.args, bound_args.kwargs
|
||||
|
||||
if forever:
|
||||
local_loop.create_task(_run_forever_coro(
|
||||
coro, args, kwargs, local_loop
|
||||
))
|
||||
local_loop.run_forever()
|
||||
else:
|
||||
return local_loop.run_until_complete(coro(*args, **kwargs))
|
||||
|
||||
# Attach the updated signature. This allows 'pass_loop' to be used with
|
||||
# autoparse
|
||||
if pass_loop:
|
||||
autoasync_wrapper.__signature__ = new_sig
|
||||
|
||||
return autoasync_wrapper
|
|
@ -0,0 +1,70 @@
|
|||
# Copyright 2014-2015 Nathan West
|
||||
#
|
||||
# This file is part of autocommand.
|
||||
#
|
||||
# autocommand is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# autocommand is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with autocommand. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .autoparse import autoparse
|
||||
from .automain import automain
|
||||
try:
|
||||
from .autoasync import autoasync
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
def autocommand(
|
||||
module, *,
|
||||
description=None,
|
||||
epilog=None,
|
||||
add_nos=False,
|
||||
parser=None,
|
||||
loop=None,
|
||||
forever=False,
|
||||
pass_loop=False):
|
||||
|
||||
if callable(module):
|
||||
raise TypeError('autocommand requires a module name argument')
|
||||
|
||||
def autocommand_decorator(func):
|
||||
# Step 1: if requested, run it all in an asyncio event loop. autoasync
|
||||
# patches the __signature__ of the decorated function, so that in the
|
||||
# event that pass_loop is True, the `loop` parameter of the original
|
||||
# function will *not* be interpreted as a command-line argument by
|
||||
# autoparse
|
||||
if loop is not None or forever or pass_loop:
|
||||
func = autoasync(
|
||||
func,
|
||||
loop=None if loop is True else loop,
|
||||
pass_loop=pass_loop,
|
||||
forever=forever)
|
||||
|
||||
# Step 2: create parser. We do this second so that the arguments are
|
||||
# parsed and passed *before* entering the asyncio event loop, if it
|
||||
# exists. This simplifies the stack trace and ensures errors are
|
||||
# reported earlier. It also ensures that errors raised during parsing &
|
||||
# passing are still raised if `forever` is True.
|
||||
func = autoparse(
|
||||
func,
|
||||
description=description,
|
||||
epilog=epilog,
|
||||
add_nos=add_nos,
|
||||
parser=parser)
|
||||
|
||||
# Step 3: call the function automatically if __name__ == '__main__' (or
|
||||
# if True was provided)
|
||||
func = automain(module)(func)
|
||||
|
||||
return func
|
||||
|
||||
return autocommand_decorator
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright 2014-2015 Nathan West
|
||||
#
|
||||
# This file is part of autocommand.
|
||||
#
|
||||
# autocommand is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# autocommand is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with autocommand. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
from .errors import AutocommandError
|
||||
|
||||
|
||||
class AutomainRequiresModuleError(AutocommandError, TypeError):
|
||||
pass
|
||||
|
||||
|
||||
def automain(module, *, args=(), kwargs=None):
|
||||
'''
|
||||
This decorator automatically invokes a function if the module is being run
|
||||
as the "__main__" module. Optionally, provide args or kwargs with which to
|
||||
call the function. If `module` is "__main__", the function is called, and
|
||||
the program is `sys.exit`ed with the return value. You can also pass `True`
|
||||
to cause the function to be called unconditionally. If the function is not
|
||||
called, it is returned unchanged by the decorator.
|
||||
|
||||
Usage:
|
||||
|
||||
@automain(__name__) # Pass __name__ to check __name__=="__main__"
|
||||
def main():
|
||||
...
|
||||
|
||||
If __name__ is "__main__" here, the main function is called, and then
|
||||
sys.exit called with the return value.
|
||||
'''
|
||||
|
||||
# Check that @automain(...) was called, rather than @automain
|
||||
if callable(module):
|
||||
raise AutomainRequiresModuleError(module)
|
||||
|
||||
if module == '__main__' or module is True:
|
||||
if kwargs is None:
|
||||
kwargs = {}
|
||||
|
||||
# Use a function definition instead of a lambda for a neater traceback
|
||||
def automain_decorator(main):
|
||||
sys.exit(main(*args, **kwargs))
|
||||
|
||||
return automain_decorator
|
||||
else:
|
||||
return lambda main: main
|
|
@ -0,0 +1,333 @@
|
|||
# Copyright 2014-2015 Nathan West
|
||||
#
|
||||
# This file is part of autocommand.
|
||||
#
|
||||
# autocommand is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# autocommand is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with autocommand. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
from re import compile as compile_regex
|
||||
from inspect import signature, getdoc, Parameter
|
||||
from argparse import ArgumentParser
|
||||
from contextlib import contextmanager
|
||||
from functools import wraps
|
||||
from io import IOBase
|
||||
from autocommand.errors import AutocommandError
|
||||
|
||||
|
||||
_empty = Parameter.empty
|
||||
|
||||
|
||||
class AnnotationError(AutocommandError):
|
||||
'''Annotation error: annotation must be a string, type, or tuple of both'''
|
||||
|
||||
|
||||
class PositionalArgError(AutocommandError):
|
||||
'''
|
||||
Postional Arg Error: autocommand can't handle postional-only parameters
|
||||
'''
|
||||
|
||||
|
||||
class KWArgError(AutocommandError):
|
||||
'''kwarg Error: autocommand can't handle a **kwargs parameter'''
|
||||
|
||||
|
||||
class DocstringError(AutocommandError):
|
||||
'''Docstring error'''
|
||||
|
||||
|
||||
class TooManySplitsError(DocstringError):
|
||||
'''
|
||||
The docstring had too many ---- section splits. Currently we only support
|
||||
using up to a single split, to split the docstring into description and
|
||||
epilog parts.
|
||||
'''
|
||||
|
||||
|
||||
def _get_type_description(annotation):
|
||||
'''
|
||||
Given an annotation, return the (type, description) for the parameter.
|
||||
If you provide an annotation that is somehow both a string and a callable,
|
||||
the behavior is undefined.
|
||||
'''
|
||||
if annotation is _empty:
|
||||
return None, None
|
||||
elif callable(annotation):
|
||||
return annotation, None
|
||||
elif isinstance(annotation, str):
|
||||
return None, annotation
|
||||
elif isinstance(annotation, tuple):
|
||||
try:
|
||||
arg1, arg2 = annotation
|
||||
except ValueError as e:
|
||||
raise AnnotationError(annotation) from e
|
||||
else:
|
||||
if callable(arg1) and isinstance(arg2, str):
|
||||
return arg1, arg2
|
||||
elif isinstance(arg1, str) and callable(arg2):
|
||||
return arg2, arg1
|
||||
|
||||
raise AnnotationError(annotation)
|
||||
|
||||
|
||||
def _add_arguments(param, parser, used_char_args, add_nos):
|
||||
'''
|
||||
Add the argument(s) to an ArgumentParser (using add_argument) for a given
|
||||
parameter. used_char_args is the set of -short options currently already in
|
||||
use, and is updated (if necessary) by this function. If add_nos is True,
|
||||
this will also add an inverse switch for all boolean options. For
|
||||
instance, for the boolean parameter "verbose", this will create --verbose
|
||||
and --no-verbose.
|
||||
'''
|
||||
|
||||
# Impl note: This function is kept separate from make_parser because it's
|
||||
# already very long and I wanted to separate out as much as possible into
|
||||
# its own call scope, to prevent even the possibility of suble mutation
|
||||
# bugs.
|
||||
if param.kind is param.POSITIONAL_ONLY:
|
||||
raise PositionalArgError(param)
|
||||
elif param.kind is param.VAR_KEYWORD:
|
||||
raise KWArgError(param)
|
||||
|
||||
# These are the kwargs for the add_argument function.
|
||||
arg_spec = {}
|
||||
is_option = False
|
||||
|
||||
# Get the type and default from the annotation.
|
||||
arg_type, description = _get_type_description(param.annotation)
|
||||
|
||||
# Get the default value
|
||||
default = param.default
|
||||
|
||||
# If there is no explicit type, and the default is present and not None,
|
||||
# infer the type from the default.
|
||||
if arg_type is None and default not in {_empty, None}:
|
||||
arg_type = type(default)
|
||||
|
||||
# Add default. The presence of a default means this is an option, not an
|
||||
# argument.
|
||||
if default is not _empty:
|
||||
arg_spec['default'] = default
|
||||
is_option = True
|
||||
|
||||
# Add the type
|
||||
if arg_type is not None:
|
||||
# Special case for bool: make it just a --switch
|
||||
if arg_type is bool:
|
||||
if not default or default is _empty:
|
||||
arg_spec['action'] = 'store_true'
|
||||
else:
|
||||
arg_spec['action'] = 'store_false'
|
||||
|
||||
# Switches are always options
|
||||
is_option = True
|
||||
|
||||
# Special case for file types: make it a string type, for filename
|
||||
elif isinstance(default, IOBase):
|
||||
arg_spec['type'] = str
|
||||
|
||||
# TODO: special case for list type.
|
||||
# - How to specificy type of list members?
|
||||
# - param: [int]
|
||||
# - param: int =[]
|
||||
# - action='append' vs nargs='*'
|
||||
|
||||
else:
|
||||
arg_spec['type'] = arg_type
|
||||
|
||||
# nargs: if the signature includes *args, collect them as trailing CLI
|
||||
# arguments in a list. *args can't have a default value, so it can never be
|
||||
# an option.
|
||||
if param.kind is param.VAR_POSITIONAL:
|
||||
# TODO: consider depluralizing metavar/name here.
|
||||
arg_spec['nargs'] = '*'
|
||||
|
||||
# Add description.
|
||||
if description is not None:
|
||||
arg_spec['help'] = description
|
||||
|
||||
# Get the --flags
|
||||
flags = []
|
||||
name = param.name
|
||||
|
||||
if is_option:
|
||||
# Add the first letter as a -short option.
|
||||
for letter in name[0], name[0].swapcase():
|
||||
if letter not in used_char_args:
|
||||
used_char_args.add(letter)
|
||||
flags.append('-{}'.format(letter))
|
||||
break
|
||||
|
||||
# If the parameter is a --long option, or is a -short option that
|
||||
# somehow failed to get a flag, add it.
|
||||
if len(name) > 1 or not flags:
|
||||
flags.append('--{}'.format(name))
|
||||
|
||||
arg_spec['dest'] = name
|
||||
else:
|
||||
flags.append(name)
|
||||
|
||||
parser.add_argument(*flags, **arg_spec)
|
||||
|
||||
# Create the --no- version for boolean switches
|
||||
if add_nos and arg_type is bool:
|
||||
parser.add_argument(
|
||||
'--no-{}'.format(name),
|
||||
action='store_const',
|
||||
dest=name,
|
||||
const=default if default is not _empty else False)
|
||||
|
||||
|
||||
def make_parser(func_sig, description, epilog, add_nos):
|
||||
'''
|
||||
Given the signature of a function, create an ArgumentParser
|
||||
'''
|
||||
parser = ArgumentParser(description=description, epilog=epilog)
|
||||
|
||||
used_char_args = {'h'}
|
||||
|
||||
# Arange the params so that single-character arguments are first. This
|
||||
# esnures they don't have to get --long versions. sorted is stable, so the
|
||||
# parameters will otherwise still be in relative order.
|
||||
params = sorted(
|
||||
func_sig.parameters.values(),
|
||||
key=lambda param: len(param.name) > 1)
|
||||
|
||||
for param in params:
|
||||
_add_arguments(param, parser, used_char_args, add_nos)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
_DOCSTRING_SPLIT = compile_regex(r'\n\s*-{4,}\s*\n')
|
||||
|
||||
|
||||
def parse_docstring(docstring):
|
||||
'''
|
||||
Given a docstring, parse it into a description and epilog part
|
||||
'''
|
||||
if docstring is None:
|
||||
return '', ''
|
||||
|
||||
parts = _DOCSTRING_SPLIT.split(docstring)
|
||||
|
||||
if len(parts) == 1:
|
||||
return docstring, ''
|
||||
elif len(parts) == 2:
|
||||
return parts[0], parts[1]
|
||||
else:
|
||||
raise TooManySplitsError()
|
||||
|
||||
|
||||
def autoparse(
|
||||
func=None, *,
|
||||
description=None,
|
||||
epilog=None,
|
||||
add_nos=False,
|
||||
parser=None):
|
||||
'''
|
||||
This decorator converts a function that takes normal arguments into a
|
||||
function which takes a single optional argument, argv, parses it using an
|
||||
argparse.ArgumentParser, and calls the underlying function with the parsed
|
||||
arguments. If it is not given, sys.argv[1:] is used. This is so that the
|
||||
function can be used as a setuptools entry point, as well as a normal main
|
||||
function. sys.argv[1:] is not evaluated until the function is called, to
|
||||
allow injecting different arguments for testing.
|
||||
|
||||
It uses the argument signature of the function to create an
|
||||
ArgumentParser. Parameters without defaults become positional parameters,
|
||||
while parameters *with* defaults become --options. Use annotations to set
|
||||
the type of the parameter.
|
||||
|
||||
The `desctiption` and `epilog` parameters corrospond to the same respective
|
||||
argparse parameters. If no description is given, it defaults to the
|
||||
decorated functions's docstring, if present.
|
||||
|
||||
If add_nos is True, every boolean option (that is, every parameter with a
|
||||
default of True/False or a type of bool) will have a --no- version created
|
||||
as well, which inverts the option. For instance, the --verbose option will
|
||||
have a --no-verbose counterpart. These are not mutually exclusive-
|
||||
whichever one appears last in the argument list will have precedence.
|
||||
|
||||
If a parser is given, it is used instead of one generated from the function
|
||||
signature. In this case, no parser is created; instead, the given parser is
|
||||
used to parse the argv argument. The parser's results' argument names must
|
||||
match up with the parameter names of the decorated function.
|
||||
|
||||
The decorated function is attached to the result as the `func` attribute,
|
||||
and the parser is attached as the `parser` attribute.
|
||||
'''
|
||||
|
||||
# If @autoparse(...) is used instead of @autoparse
|
||||
if func is None:
|
||||
return lambda f: autoparse(
|
||||
f, description=description,
|
||||
epilog=epilog,
|
||||
add_nos=add_nos,
|
||||
parser=parser)
|
||||
|
||||
func_sig = signature(func)
|
||||
|
||||
docstr_description, docstr_epilog = parse_docstring(getdoc(func))
|
||||
|
||||
if parser is None:
|
||||
parser = make_parser(
|
||||
func_sig,
|
||||
description or docstr_description,
|
||||
epilog or docstr_epilog,
|
||||
add_nos)
|
||||
|
||||
@wraps(func)
|
||||
def autoparse_wrapper(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv[1:]
|
||||
|
||||
# Get empty argument binding, to fill with parsed arguments. This
|
||||
# object does all the heavy lifting of turning named arguments into
|
||||
# into correctly bound *args and **kwargs.
|
||||
parsed_args = func_sig.bind_partial()
|
||||
parsed_args.arguments.update(vars(parser.parse_args(argv)))
|
||||
|
||||
return func(*parsed_args.args, **parsed_args.kwargs)
|
||||
|
||||
# TODO: attach an updated __signature__ to autoparse_wrapper, just in case.
|
||||
|
||||
# Attach the wrapped function and parser, and return the wrapper.
|
||||
autoparse_wrapper.func = func
|
||||
autoparse_wrapper.parser = parser
|
||||
return autoparse_wrapper
|
||||
|
||||
|
||||
@contextmanager
|
||||
def smart_open(filename_or_file, *args, **kwargs):
|
||||
'''
|
||||
This context manager allows you to open a filename, if you want to default
|
||||
some already-existing file object, like sys.stdout, which shouldn't be
|
||||
closed at the end of the context. If the filename argument is a str, bytes,
|
||||
or int, the file object is created via a call to open with the given *args
|
||||
and **kwargs, sent to the context, and closed at the end of the context,
|
||||
just like "with open(filename) as f:". If it isn't one of the openable
|
||||
types, the object simply sent to the context unchanged, and left unclosed
|
||||
at the end of the context. Example:
|
||||
|
||||
def work_with_file(name=sys.stdout):
|
||||
with smart_open(name) as f:
|
||||
# Works correctly if name is a str filename or sys.stdout
|
||||
print("Some stuff", file=f)
|
||||
# If it was a filename, f is closed at the end here.
|
||||
'''
|
||||
if isinstance(filename_or_file, (str, bytes, int)):
|
||||
with open(filename_or_file, *args, **kwargs) as file:
|
||||
yield file
|
||||
else:
|
||||
yield filename_or_file
|
|
@ -0,0 +1,23 @@
|
|||
# Copyright 2014-2016 Nathan West
|
||||
#
|
||||
# This file is part of autocommand.
|
||||
#
|
||||
# autocommand is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# autocommand is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with autocommand. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class AutocommandError(Exception):
|
||||
'''Base class for autocommand exceptions'''
|
||||
pass
|
||||
|
||||
# Individual modules will define errors specific to that module.
|
Loading…
Add table
Add a link
Reference in a new issue