Skip to content

Commit

Permalink
Merge branch 'release/0.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
timothycrosley committed Aug 11, 2015
2 parents 130edb4 + 6bd7d96 commit 1ff0586
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 34 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
HUG
![HUG](https://raw.github.com/timothycrosley/hug/develop/logo.png)
===================

Everyone needs a hug every once in a while. Even API developers. Hug aims to make developing Python driven APIs as simple as possible, but no simpler.

[![PyPI version](https://badge.fury.io/py/hug.png)](http://badge.fury.io/py/hug)
[![Build Status](https://travis-ci.org/timothycrosley/hug.png?branch=master)](https://travis-ci.org/timothycrosley/hug)
[![Coverage Status](https://coveralls.io/repos/timothycrosley/hug/badge.svg?branch=master&service=github)](https://coveralls.io/github/timothycrosley/hug?branch=master)
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://pypi.python.org/pypi/hug/)

Hug drastically simplifies Python API development.
Hug aims to make developing Python driven APIs as simple as possible, but no simpler. As a result, it drastically simplifies Python API development.

Hug's Design Objectives:

Expand All @@ -17,6 +15,18 @@ Hug's Design Objectives:
- It should be fast. Never should a developer feel the need to look somewhere else for performance reasons.
- Writing tests for APIs written on-top of Hug should be easy and intuitive.
- Magic done once, in an API, is better then pushing the problem set to the user of the API.
- Be the basis for next generation Python APIs, embracing the latest technology.

As a result of these goals Hug is Python3+ only and uses Falcon under the cover to quickly handle requests.

Installing Hug
===================

Installing hug is as simple as:

pip install hug

Ideally, within a virtual environment.


Basic Example API
Expand Down Expand Up @@ -99,7 +109,7 @@ To run the hello world hug example API.
Why Hug?
===================
HUG simply stands for Hopefully Useful Guide. This represents the projects goal to help guide developers into creating
well written and intuitive APIs. Also, it's cheerful :)
well written and intuitive APIs.

--------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion hug/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
OTHER DEALINGS IN THE SOFTWARE.
"""
current = "0.1.0"
current = "0.2.0"
33 changes: 20 additions & 13 deletions hug/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@

class HugAPI(object):
'''Stores the information necessary to expose API calls within this module externally'''
__slots__ = ('versions', 'routes', '_output_format', '_input_format', '_directives')
__slots__ = ('versions', 'routes', '_output_format', '_input_format', '_directives', 'versioned')

def __init__(self):
self.versions = set()
self.routes = OrderedDict()
self.versioned = OrderedDict()

@property
def output_format(self):
Expand Down Expand Up @@ -138,7 +139,7 @@ def decorator(api_function):
if not required and not use_examples:
use_examples = (True, )

def interface(request, response, **kwargs):
def interface(request, response, api_version=None, **kwargs):
response.content_type = function_output.content_type
input_parameters = kwargs
input_parameters.update(request.params)
Expand All @@ -162,9 +163,12 @@ def interface(request, response, **kwargs):
input_parameters['request'] = request
if 'response' in accepted_parameters:
input_parameters['response'] = response
if 'api_version' in accepted_parameters:
input_parameters['api_version'] = api_version
for parameter in use_directives:
arguments = (defaults[parameter], ) if parameter in defaults else ()
input_parameters[parameter] = directives[parameter](*arguments, response=response, request=request)
input_parameters[parameter] = directives[parameter](*arguments, response=response, request=request,
module=module, api_version=api_version)
for require in required:
if not require in input_parameters:
errors[require] = "Required parameter not supplied"
Expand All @@ -181,12 +185,24 @@ def interface(request, response, **kwargs):
if versions:
module.__hug__.versions.update(versions)

callable_method = api_function
if use_directives:
@wraps(api_function)
def callable_method(*args, **kwargs):
for parameter in use_directives:
arguments = (defaults[parameter], ) if parameter in defaults else ()
kwargs[parameter] = directives[parameter](*arguments, module=module,
api_version=max(versions) if versions else None)
return api_function(*args, **kwargs)
callable_method.interface = interface

for url in urls or ("/{0}".format(api_function.__name__), ):
handlers = module.__hug__.routes.setdefault(url, {})
for method in accept:
version_mapping = handlers.setdefault(method.upper(), {})
for version in versions:
version_mapping[version] = interface
module.__hug__.versioned.setdefault(version, {})[callable_method.__name__] = callable_method

api_function.interface = interface
interface.api_function = api_function
Expand All @@ -196,16 +212,7 @@ def interface(request, response, **kwargs):
interface.accepted_parameters = accepted_parameters
interface.content_type = function_output.content_type

if use_directives:
@wraps(api_function)
def directive_injected_method(*args, **kwargs):
for parameter in use_directives:
arguments = (defaults[parameter], ) if parameter in defaults else ()
kwargs[parameter] = directives[parameter](*arguments)
return api_function(*args, **kwargs)
return directive_injected_method
else:
return api_function
return callable_method
return decorator


Expand Down
3 changes: 2 additions & 1 deletion hug/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@

output_format = hug.output_format.json
input_format = {'application/json': hug.input_format.json}
directives = {'timer': hug.directives.timer}
directives = {'timer': hug.directives.Timer, 'api': hug.directives.api, 'module': hug.directives.module,
'current_api': hug.directives.CurrentAPI}
52 changes: 46 additions & 6 deletions hug/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,53 @@


class Timer(object):
__slots__ = ('start', 'format')
'''Keeps track of time surpased since instanciation, outputed by doing float(instance)'''
__slots__ = ('start', 'round_to')

def __init__(self, format=None, **kwargs):
def __init__(self, round_to=None, **kwargs):
self.start = python_timer()
self.format = format
self.round_to = round_to

def taken(self):
return (self.format.format if self.format else float)(python_timer() - self.start)
@property
def elapsed(self):
return round(self.start, self.round_to) if self.round_to else self.start

def __float__(self):
return self.elapsed

def __int__(self):
return int(round(self.elapsed))


def module(default=None, module=None, **kwargs):
'''Returns the module that is running this hug API function'''
if not module:
return default

return module


def api(default=None, module=None, **kwargs):
'''Returns the api instance in which this API function is being ran'''
return getattr(module, '__hug__', default)


class CurrentAPI(object):
'''Returns quick access to all api functions on the current version of the api'''
__slots__ = ('api_version', 'api')

def __init__(self, default=None, api_version=None, **kwargs):
self.api_version = api_version
self.api = api(**kwargs)

def __getattr__(self, name):
function = self.api.versioned.get(self.api_version, {}).get(name, None)
if function:
return function

function = self.api.versioned.get(None, {}).get(name, None)
if function:
return function

raise AttributeError('API Function {0} not found'.format(name))

timer = Timer
4 changes: 3 additions & 1 deletion hug/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ def generate(module, base_url=""):
example_text = "{0}{1}{2}".format(base_url, '/v{0}'.format(version) if version else '', url)
if isinstance(example, str):
example_text += "?{0}".format(example)
doc.setdefault('examples', []).append(example_text)
doc_examples = doc.setdefault('examples', [])
if not example_text in doc_examples:
doc_examples.append(example_text)
doc['outputs'] = OrderedDict(format=handler.output_format.__doc__,
content_type=handler.content_type)

Expand Down
26 changes: 25 additions & 1 deletion hug/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@
from hug import documentation
from hug._version import current

INTRO = """
/#######################################################################\\
`.----``..-------..``.----.
:/:::::--:---------:--::::://.
.+::::----##/-/oo+:-##----:::://
`//::-------/oosoo-------::://. ## ## ## ## #####
.-:------./++o/o-.------::-` ``` ## ## ## ## ##
`----.-./+o+:..----. `.:///. ######## ## ## ##
``` `----.-::::::------ `.-:::://. ## ## ## ## ## ####
://::--.``` -:``...-----...` `:--::::::-.` ## ## ## ## ## ##
:/:::::::::-:- ````` .:::::-.` ## ## #### ######
``.--:::::::. .:::.`
``..::. .:: EMBRACE THE APIs OF THE FUTURE
::- .:-
-::` ::- VERSION {0}
`::- -::`
-::-` -::-
\########################################################################/
Copyright (C) 2015 Timothy Edmund Crosley
Under the MIT License
""".format(current)

def documentation_404(module):
def handle_404(request, response, *kargs, **kwargs):
Expand Down Expand Up @@ -50,7 +73,7 @@ def version_router(request, response, api_version=None, __versions__={}, __sink_
if request_version:
request_version = int(request_version)
__versions__.get(request_version, __versions__.get(None, __sink__))(request, response, api_version=api_version,
**kwargs)
**kwargs)

def server(module, sink=documentation_404):
api = falcon.API()
Expand Down Expand Up @@ -93,6 +116,7 @@ def terminal():
if not api:
sys.exit(1)

print(INTRO)
httpd = make_server('', parsed.port, api)
print("Serving on port {0}...".format(parsed.port))
httpd.serve_forever()
Binary file added logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added logo.xcf
Binary file not shown.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def run(self):
readme = ''

setup(name='hug',
version='0.1.0',
version='0.2.0',
description='A Python framework that makes developing APIs as simple as possible, but no simpler.',
long_description=readme,
author='Timothy Crosley',
Expand Down
14 changes: 9 additions & 5 deletions tests/test_directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,21 @@


def test_timer():
timer = hug.directives.timer()
assert isinstance(timer.taken(), float)
timer = hug.directives.Timer()
assert isinstance(timer.start, float)
assert isinstance(timer.elapsed, float)
assert isinstance(float(timer), float)
assert isinstance(int(timer), int)

timer = hug.directives.timer('Time: {0}')
assert isinstance(timer.taken(), str)
timer = hug.directives.Timer(3)
assert isinstance(timer.start, float)
assert isinstance(timer.elapsed, float)
assert isinstance(float(timer), float)
assert isinstance(int(timer), int)

@hug.get()
def timer_tester(hug_timer):
return hug_timer.taken()
return float(hug_timer)

assert isinstance(hug.test.get(api, 'timer_tester').data, float)
assert isinstance(timer_tester(), float)
Expand Down

0 comments on commit 1ff0586

Please sign in to comment.