1#    ___ 
    2#    \./     DANGER: This project implements some code generation 
    3# .--.O.--.          techniques involving string concatenation. 
    4#  \/   \/           If you look at it, you might die. 
    5# 
    6 
    7r""" 
    8Installation 
    9************ 
    10 
    11.. code-block:: bash 
    12 
    13    pip install fastjsonschema 
    14 
    15Support only for Python 3.3 and higher. 
    16 
    17About 
    18***** 
    19 
    20``fastjsonschema`` implements validation of JSON documents by JSON schema. 
    21The library implements JSON schema drafts 04, 06, and 07. The main purpose is 
    22to have a really fast implementation. See some numbers: 
    23 
    24 * Probably the most popular, ``jsonschema``, can take up to 5 seconds for valid 
    25   inputs and 1.2 seconds for invalid inputs. 
    26 * Second most popular, ``json-spec``, is even worse with up to 7.2 and 1.7 seconds. 
    27 * Last ``validictory``, now deprecated, is much better with 370 or 23 milliseconds, 
    28   but it does not follow all standards, and it can be still slow for some purposes. 
    29 
    30With this library you can gain big improvements as ``fastjsonschema`` takes 
    31only about 25 milliseconds for valid inputs and 2 milliseconds for invalid ones. 
    32Pretty amazing, right? :-) 
    33 
    34Technically it works by generating the most stupid code on the fly, which is fast but 
    35is hard to write by hand. The best efficiency is achieved when a validator is compiled 
    36once and used many times, of course. It works similarly like regular expressions. But 
    37you can also generate the code to a file, which is even slightly faster. 
    38 
    39You can run the performance benchmarks on your computer or server with the included 
    40script: 
    41 
    42.. code-block:: bash 
    43 
    44    $ make performance 
    45    fast_compiled        valid      ==>  0.0464646 
    46    fast_compiled        invalid    ==>  0.0030227 
    47    fast_file            valid      ==>  0.0461219 
    48    fast_file            invalid    ==>  0.0030608 
    49    fast_not_compiled    valid      ==> 11.4627202 
    50    fast_not_compiled    invalid    ==>  2.5726230 
    51    jsonschema           valid      ==>  7.5844927 
    52    jsonschema           invalid    ==>  1.9204665 
    53    jsonschema_compiled  valid      ==>  0.6938364 
    54    jsonschema_compiled  invalid    ==>  0.0359244 
    55    jsonspec             valid      ==>  9.0715843 
    56    jsonspec             invalid    ==>  2.1650488 
    57    validictory          valid      ==>  0.4874793 
    58    validictory          invalid    ==>  0.0232244 
    59 
    60This library follows and implements `JSON schema draft-04, draft-06, and draft-07 
    61<http://json-schema.org>`_. Sometimes it's not perfectly clear, so I recommend also 
    62check out this `understanding JSON schema <https://spacetelescope.github.io/understanding-json-schema>`_. 
    63 
    64Note that there are some differences compared to JSON schema standard: 
    65 
    66 * Regular expressions are full Python ones, not only what JSON schema allows. It's easier 
    67   to allow everything, and also it's faster to compile without limits. So keep in mind that when 
    68   you will use a more advanced regular expression, it may not work with other libraries or in 
    69   other languages. 
    70 * Because Python matches new line for a dollar in regular expressions (``a$`` matches ``a`` and ``a\\n``), 
    71   instead of ``$`` is used ``\Z`` and all dollars in your regular expression are changed to ``\\Z`` 
    72   as well. When you want to use dollar as regular character, you have to escape it (``\$``). 
    73 * JSON schema says you can use keyword ``default`` for providing default values. This implementation 
    74   uses that and always returns transformed input data. 
    75 
    76Usage 
    77***** 
    78 
    79.. code-block:: python 
    80 
    81    import fastjsonschema 
    82 
    83    point_schema = { 
    84        "type": "object", 
    85        "properties": { 
    86            "x": { 
    87                "type": "number", 
    88            }, 
    89            "y": { 
    90                "type": "number", 
    91            }, 
    92        }, 
    93        "required": ["x", "y"], 
    94        "additionalProperties": False, 
    95    } 
    96 
    97    point_validator = fastjsonschema.compile(point_schema) 
    98    try: 
    99        point_validator({"x": 1.0, "y": 2.0}) 
    100    except fastjsonschema.JsonSchemaException as e: 
    101        print(f"Data failed validation: {e}") 
    102 
    103API 
    104*** 
    105""" 
    106from functools import partial, update_wrapper 
    107 
    108from .draft04 import CodeGeneratorDraft04 
    109from .draft06 import CodeGeneratorDraft06 
    110from .draft07 import CodeGeneratorDraft07 
    111from .exceptions import JsonSchemaException, JsonSchemaValueException, JsonSchemaDefinitionException 
    112from .ref_resolver import RefResolver 
    113from .version import VERSION 
    114 
    115__all__ = ( 
    116    'VERSION', 
    117    'JsonSchemaException', 
    118    'JsonSchemaValueException', 
    119    'JsonSchemaDefinitionException', 
    120    'validate', 
    121    'compile', 
    122    'compile_to_code', 
    123) 
    124 
    125 
    126def validate(definition, data, handlers={}, formats={}, use_default=True, use_formats=True): 
    127    """ 
    128    Validation function for lazy programmers or for use cases when you need 
    129    to call validation only once, so you do not have to compile it first. 
    130    Use it only when you do not care about performance (even though it will 
    131    be still faster than alternative implementations). 
    132 
    133    .. code-block:: python 
    134 
    135        import fastjsonschema 
    136 
    137        fastjsonschema.validate({'type': 'string'}, 'hello') 
    138        # same as: compile({'type': 'string'})('hello') 
    139 
    140    Preferred is to use :any:`compile` function. 
    141    """ 
    142    return compile(definition, handlers, formats, use_default, use_formats)(data) 
    143 
    144 
    145#TODO: Change use_default to False when upgrading to version 3. 
    146# pylint: disable=redefined-builtin,dangerous-default-value,exec-used 
    147def compile(definition, handlers={}, formats={}, use_default=True, use_formats=True): 
    148    """ 
    149    Generates validation function for validating JSON schema passed in ``definition``. 
    150    Example: 
    151 
    152    .. code-block:: python 
    153 
    154        import fastjsonschema 
    155 
    156        validate = fastjsonschema.compile({'type': 'string'}) 
    157        validate('hello') 
    158 
    159    This implementation supports keyword ``default`` (can be turned off 
    160    by passing `use_default=False`): 
    161 
    162    .. code-block:: python 
    163 
    164        validate = fastjsonschema.compile({ 
    165            'type': 'object', 
    166            'properties': { 
    167                'a': {'type': 'number', 'default': 42}, 
    168            }, 
    169        }) 
    170 
    171        data = validate({}) 
    172        assert data == {'a': 42} 
    173 
    174    Supported implementations are draft-04, draft-06 and draft-07. Which version 
    175    should be used is determined by `$draft` in your ``definition``. When not 
    176    specified, the latest implementation is used (draft-07). 
    177 
    178    .. code-block:: python 
    179 
    180        validate = fastjsonschema.compile({ 
    181            '$schema': 'http://json-schema.org/draft-04/schema', 
    182            'type': 'number', 
    183        }) 
    184 
    185    You can pass mapping from URI to function that should be used to retrieve 
    186    remote schemes used in your ``definition`` in parameter ``handlers``. 
    187 
    188    Also, you can pass mapping for custom formats. Key is the name of your 
    189    formatter and value can be regular expression, which will be compiled or 
    190    callback returning `bool` (or you can raise your own exception). 
    191 
    192    .. code-block:: python 
    193 
    194        validate = fastjsonschema.compile(definition, formats={ 
    195            'foo': r'foo|bar', 
    196            'bar': lambda value: value in ('foo', 'bar'), 
    197        }) 
    198 
    199    Note that formats are automatically used as assertions. It can be turned 
    200    off by passing `use_formats=False`. When disabled, custom formats are 
    201    disabled as well. (Added in 2.19.0.) 
    202 
    203    Exception :any:`JsonSchemaDefinitionException` is raised when generating the 
    204    code fails (bad definition). 
    205 
    206    Exception :any:`JsonSchemaValueException` is raised from generated function when 
    207    validation fails (data do not follow the definition). 
    208    """ 
    209    resolver, code_generator = _factory(definition, handlers, formats, use_default, use_formats) 
    210    global_state = code_generator.global_state 
    211    # Do not pass local state so it can recursively call itself. 
    212    exec(code_generator.func_code, global_state) 
    213    func = global_state[resolver.get_scope_name()] 
    214    if formats: 
    215        return update_wrapper(partial(func, custom_formats=formats), func) 
    216    return func 
    217 
    218 
    219# pylint: disable=dangerous-default-value 
    220def compile_to_code(definition, handlers={}, formats={}, use_default=True, use_formats=True): 
    221    """ 
    222    Generates validation code for validating JSON schema passed in ``definition``. 
    223    Example: 
    224 
    225    .. code-block:: python 
    226 
    227        import fastjsonschema 
    228 
    229        code = fastjsonschema.compile_to_code({'type': 'string'}) 
    230        with open('your_file.py', 'w') as f: 
    231            f.write(code) 
    232 
    233    You can also use it as a script: 
    234 
    235    .. code-block:: bash 
    236 
    237        echo "{'type': 'string'}" | python3 -m fastjsonschema > your_file.py 
    238        python3 -m fastjsonschema "{'type': 'string'}" > your_file.py 
    239 
    240    Exception :any:`JsonSchemaDefinitionException` is raised when generating the 
    241    code fails (bad definition). 
    242    """ 
    243    _, code_generator = _factory(definition, handlers, formats, use_default, use_formats) 
    244    return ( 
    245        'VERSION = "' + VERSION + '"\n' + 
    246        code_generator.global_state_code + '\n' + 
    247        code_generator.func_code 
    248    ) 
    249 
    250 
    251def _factory(definition, handlers, formats={}, use_default=True, use_formats=True): 
    252    resolver = RefResolver.from_schema(definition, handlers=handlers, store={}) 
    253    code_generator = _get_code_generator_class(definition)( 
    254        definition, 
    255        resolver=resolver, 
    256        formats=formats, 
    257        use_default=use_default, 
    258        use_formats=use_formats, 
    259    ) 
    260    return resolver, code_generator 
    261 
    262 
    263def _get_code_generator_class(schema): 
    264    # Schema in from draft-06 can be just the boolean value. 
    265    if isinstance(schema, dict): 
    266        schema_version = schema.get('$schema', '') 
    267        if 'draft-04' in schema_version: 
    268            return CodeGeneratorDraft04 
    269        if 'draft-06' in schema_version: 
    270            return CodeGeneratorDraft06 
    271    return CodeGeneratorDraft07