<commit_before>Obiwan.py
---------

# what is obiwan?

[blogpost] http://williamedwardscoder.tumblr.com/post/33185451698/obiwan-typescript-for-python

Obiwan is a Python type-checker.  You place descriptive type constraints in your function declarations and obiwan can check them for you at runtime.

A function can look like:

    def example(a: int, b: float) -> number:
        return a/b

My ambition is that this Obiwan syntax is widely adopted and eventually Python static type checkers support it and IDEs can do auto-complete ala Typescript.

To enable obiwan, you just call it:

    from obiwan import *; install_obiwan_runtime_check()
    
you are now running obiwan!  Runtime execution will be slower, but annotated functions will be checked for parameter correctness!

All strings in your function annotations are ingored; you can place documentation in annotations without impacting obiwan.

# maturity

The dictionary and list checking code is based upon a tried-and-tested JSON validator.

The integration with Python 3 function annotations is new and the `function` and `duck` type checking is new.  Improvements and patches welcome!

# validating dictionaries and lists

You can also describe dictionary parameters and what their expected attributes are:

    def example2(obj: {"a":int, "b": float}) -> {"ret": number}:
        return {"ret": a/b}
        
Duckable checking can support the checking of *optional* and *noneable* attributes:

    def example3(obj: {"a":int, optional("b"): float}):
        ...
        
Duckable checks can contain duckable attributes themselves too:

    def example4(person: {"name":str, "phone": {"type":str, "number":str}}):
        ...
        
You can specify alternative constraint types using sets:

    def example5(x: {int,float}):
        ...
        
In fact, *number* type is just a set of int and float.  And *noneable* is just a way of saying {...,None}

Lists mean that the attribute must be an array where each element matches the constraint e.g.:

    def example6(numbers: [int]):
        ...

# validating JSON
        
Utility functions to load and dump JSON are provided.  These support a new *template* parameter and validate the input/output matches the constraint e.g.:

    json.loads(tainted,template=[{"person":....}])
    
# if it quacks like a duck...

In Python 3 everything is an object, even `int` and `None`.  So you can't generically say that an argument or attribute must be an *object*.  You have to say what its attributes should be.  This follows the same style as validating dictionaries, but uses the *duck* type and keyword arguments to define:

    def example7(a: duck(name=str,get_name=function)):
        ...
        
This means that `a` must be something with a name attribute of type string, and a function attribute called get_name.
        
# validating callbacks
        
You can say that a parameter is callable using function:

    def example8(callback: function):
        ...
        
If you want, you can describe the parameters that the function should take:

    def example9(callback: function(int,str)):
        ...
        
However, all the functions passed to example8 must now be properly annotated with a matching annotation.

The special type any can be used if you do not want to check the type:

    def example10(callback: function(int,any,number)):
        ...
        
You can also specify that a function should support further arguments using ellipsis:

    def example11(callback: function(int,any,...)):
        ...
        
This will ensure that all callbacks have at least two parameters, the first being an int.

# a special note on checking return types

In general, you can specify this as a function annotation after the arguments using `->` syntax:

    def example12() -> int:
        ...
        
The checker will ensure that this function returns an int.

Or `None`.  Unfortunately, at the current time, the checker cannot stop you from returning nothing from a function and does not catch this error.  This is being given special attention and we hope this will be remedied soon.

# writing your own custom checkers
        
You can provide your own complex custom constraint checkers by subclassing the ObiwanCheck class; look at obiwan.StringCheck for inspiration.
<commit_msg>added function and object checkers<commit_after>Obiwan.py
---------

# what is obiwan?

[blogpost] http://williamedwardscoder.tumblr.com/post/33185451698/obiwan-typescript-for-python

Obiwan is a Python type-checker.  You place descriptive type constraints in your function declarations and obiwan can check them for you at runtime.

A function can look like:

    def example(a: int, b: float) -> number:
        return a/b

My ambition is that this Obiwan syntax is widely adopted and eventually Python static type checkers support it and IDEs can do auto-complete ala Typescript.

To enable obiwan, you just call it:

    from obiwan import *; install_obiwan_runtime_check()
    
you are now running obiwan!  Runtime execution will be slower, but annotated functions will be checked for parameter correctness!

All strings in your function annotations are ingored; you can place documentation in annotations without impacting obiwan.

# maturity

The dictionary and list checking code is based upon a tried-and-tested JSON validator.

The integration with Python 3 function annotations is new and the `function` and `duck` type checking is new.  Improvements and patches welcome!

# validating dictionaries and lists

You can also describe dictionary parameters and what their expected attributes are:

    def example2(obj: {"a":int, "b": float}) -> {"ret": number}:
        return {"ret": a/b}
        
Duckable checking can support the checking of *optional* and *noneable* attributes:

    def example3(obj: {"a":int, optional("b"): float}):
        ...
        
Duckable checks can contain duckable attributes themselves too:

    def example4(person: {"name":str, "phone": {"type":str, "number":str}}):
        ...
        
You can specify alternative constraint types using sets:

    def example5(x: {int,float}):
        ...
        
In fact, *number* type is just a set of int and float.  And *noneable* is just a way of saying `{...,None}`

Lists mean that the attribute must be an array where each element matches the constraint e.g.:

    def example6(numbers: [int]):
        ...
        
It aids readability to use variables to hold definitions e.g.:

    api_add_user = {
        "name": str,
        "admin": bool,
    }
    def add_user(user: api_add_user) -> int:
        ...

# validating JSON
        
Utility functions to load and dump JSON are provided.  These support a new *template* parameter and validate the input/output matches the constraint e.g.:

    json.loads(tainted,template=[api_add_user])
    
# if it quacks like a duck...

In Python 3 everything is an object, even `int` and `None`.  So you can't generically say that an argument or attribute must be an *object*.  You have to say what its attributes should be.  This follows the same style as validating dictionaries, but uses the *duck* type and keyword arguments to define:

    def example7(a: duck(name=str,get_name=function)):
        ...
        
This means that `a` must be something with a name attribute of type string, and a function attribute called get_name.

You can of course use classes to:

    class Person:
       def get_name(self):
          ...

    def example8(person: Person):
        ...        
        
# validating callbacks
        
You can say that a parameter is callable using function:

    def example9(callback: function):
        ...
        
If you want, you can describe the parameters that the function should take:

    def example10(callback: function(int,str)):
        ...
        
However, all the functions passed to example8 must now be properly annotated with a matching annotation.

The special type any can be used if you do not want to check the type:

    def example11(callback: function(int,any,number)):
        ...
        
You can also specify that a function should support further arguments using ellipsis:

    def example12(callback: function(int,any,...)):
        ...
        
This will ensure that all callbacks have at least two parameters, the first being an int.

# a special note on checking return types

In general, you can specify this as a function annotation after the arguments using `->` syntax:

    def example13() -> int:
        ...
        
The checker will ensure that this function returns an int.

Or `None`.  Unfortunately, at the current time, the checker cannot stop you from returning nothing from a function and does not catch this error.  This is being given special attention and we hope this will be remedied soon.

# writing your own custom checkers
        
You can provide your own complex custom constraint checkers by subclassing the ObiwanCheck class; look at obiwan.StringCheck for inspiration.

{'fraction_non_alphanumeric': 0.07322222222222222, 'fraction_numerical': 0.0064444444444444445, 'mean_word_length': 3.146015660985721, 'pattern_counts': {'":': 21, '<': 3, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6027953', 'n_tokens_mistral': 2405, 'n_tokens_neox': 2298, 'n_words': 1157}