Merge branch 'jamielennox-readme'

Conflicts:
	README.rst
This commit is contained in:
Morgan Fainberg 2016-01-13 17:21:27 -08:00
commit 12ab596539
3 changed files with 46 additions and 112 deletions

View File

@ -2,22 +2,21 @@
positional
==========
A decorator which enforces only some args may be passed positionally.
|PyPi|
|Build Status|
|Documentation Status|
Intro
=====
The Basics
==========
`positional` provides a decorator which enforces only some args may be passed
positionally. The idea and some of the code was taken from the oauth2 client
of the google-api client.
The Basics
==========
The decorator makes it easy to support Python 3 style key-word only
parameters. For example, in Python 3 it is possible to write:
@ -121,18 +120,25 @@ This behaviour will work with the `positional.method` and
... @positional.classmethod()
... def my_method(cls, pos1, kwonly1=None):
... ...
MyClass.my_method(10) # Ok.
MyClass.my_method(10, 20) # Raises exception.
MyClass.my_method(10, kwonly1=20) # Ok.
...
>>> MyClass.my_method(10) # Ok.
>>> MyClass.my_method(10, 20) # Raises exception.
>>> MyClass.my_method(10, kwonly1=20) # Ok.
For compatibility reasons you may wish to not always raise an exception so
a WARN mode is available. Rather than raise an exception a warning message
will be logged:
@positional(1, enforcement=positional.WARN):
def fn(pos1, kwonly=1):
...
.. code:: python
>>> @positional(1, enforcement=positional.WARN):
... def fn(pos1, kwonly=1):
... ...
Available modes are:
- positional.EXCEPT - the default, raise an exception.
- positional.WARN - log a warning on mistake.
.. |Build Status| image:: https://travis-ci.org/morganfainberg/positional.svg?branch=master

View File

@ -6,7 +6,13 @@ Contents:
.. toctree::
:maxdepth: 1
api/positional
api/modules
Usage
=====
.. include:: ../../README.rst
Release Notes
=============

View File

@ -21,109 +21,31 @@ _logger = logging.getLogger(__name__)
class positional(object):
"""A decorator which enforces only some args may be passed positionally.
"""A decorator to enforce passing arguments as keywords.
This idea and some of the code was taken from the oauth2 client of the
google-api client.
When you have a function that takes a lot of arguments you expect people to
pass those arguments as keyword arguments. Python however does not enforce
this. In future then if you decide that you want to insert a new argument
or rearrange the arguments or transition to using **kwargs you break
compatibility with users of your code who (wrongly) gave you 20 positional
arguments.
This decorator makes it easy to support Python 3 style key-word only
parameters. For example, in Python 3 it is possible to write::
In python 3 there is syntax to prevent this however we are not all in the
position where we can write python 3 exclusive code. Positional solves the
problem in the mean time across both pythons by enforcing that certain
arguments must be past as keyword arguments.
def fn(pos1, *, kwonly1, kwonly2=None):
...
:param max_positional_args: the maixmum number of arguments that can be
passed to this function without keyword parameters. Defaults to
enforcing that every parameter with a default value must be passed as a
keyword argument.
:type max_positional_args int
All named parameters after * must be a keyword::
fn(10, 'kw1', 'kw2') # Raises exception.
fn(10, kwonly1='kw1', kwonly2='kw2') # Ok.
To replicate this behaviour with the positional decorator you simply
specify how many arguments may be passed positionally. To replicate the
example above::
@positional(1)
def fn(pos1, kwonly1=None, kwonly2=None):
...
If no default value is provided to a keyword argument, it becomes a
required keyword argument::
@positional(0)
def fn(required_kw):
...
This must be called with the keyword parameter::
fn() # Raises exception.
fn(10) # Raises exception.
fn(required_kw=10) # Ok.
When defining instance or class methods always remember that in python the
first positional argument passed is always the instance so you will need to
account for `self` and `cls`::
class MyClass(object):
@positional(2)
def my_method(self, pos1, kwonly1=None):
...
@classmethod
@positional(2)
def my_method(cls, pos1, kwonly1=None):
...
If you would prefer not to account for `self` and `cls` you can use the
`method` and `classmethod` helpers which do not consider the initial
positional argument. So the following class is exactly the same as the one
above::
class MyClass(object):
@positional.method(1)
def my_method(self, pos1, kwonly1=None):
...
@positional.classmethod(1)
def my_method(cls, pos1, kwonly1=None):
...
If a value isn't provided to the decorator then it will enforce that
every variable without a default value will be required to be a kwarg::
@positional()
def fn(pos1, kwonly1=None):
...
fn(10) # Ok.
fn(10, 20) # Raises exception.
fn(10, kwonly1=20) # Ok.
This behaviour will work with the `positional.method` and
`positional.classmethod` helper functions as well::
class MyClass(object):
@positional.classmethod()
def my_method(cls, pos1, kwonly1=None):
...
MyClass.my_method(10) # Ok.
MyClass.my_method(10, 20) # Raises exception.
MyClass.my_method(10, kwonly1=20) # Ok.
For compatibility reasons you may wish to not always raise an exception so
a WARN mode is available. Rather than raise an exception a warning message
will be logged::
@positional(1, enforcement=positional.WARN):
def fn(pos1, kwonly=1):
...
Available modes are:
- positional.EXCEPT - the default, raise an exception.
- positional.WARN - log a warning on mistake.
:param enforcement: defines the way incorrect usage is reported. Currenlty
accepts :py:attr:`positional.EXCEPT` to raise a TypeError or
:py:attr:`positional.WARN` to print a warning. A warning can be useful
for applying to functions that are already public as a deprecation
notice. Defaults to :py:attr:`positional.EXCEPT`.
"""
EXCEPT = 'except'