Move doc into readme and write shorter class doc

The class documentation is quite long because I had to explain it to
everyone that was using it when it was first reviewed. This sort of
information makes more sense in the README.

Move the documentation to the README and write a more focused class
doc string.
This commit is contained in:
Jamie Lennox 2016-01-14 10:33:39 +11:00
parent 4e44524ba4
commit 7e2d1345fe
2 changed files with 126 additions and 100 deletions

@ -1,4 +1,4 @@
TOOD: Add Data Here.
A decorator which enforces only some args may be passed positionally.
|Build Status|
@ -8,3 +8,107 @@ TOOD: Add Data Here.
:target: https://travis-ci.org/morganfainberg/positional
.. |Documentation Status| image:: https://readthedocs.org/projects/positional/badge/?version=latest
:target: http://positional.readthedocs.org/en/latest/?badge=latest
This idea and some of the code was taken from the oauth2 client of the
google-api client.
This decorator makes it easy to support Python 3 style key-word only
parameters. For example, in Python 3 it is possible to write::
def fn(pos1, *, kwonly1, kwonly2=None):
...
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::
from positional import positional
@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.

@ -19,109 +19,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'