Skip to content

Commit

Permalink
Merge pull request #751 from timothycrosley/develop
Browse files Browse the repository at this point in the history
2.4.4 Release
  • Loading branch information
timothycrosley authored Mar 21, 2019
2 parents 177cc0a + 5989151 commit a9af206
Show file tree
Hide file tree
Showing 33 changed files with 334 additions and 85 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.4.3
current_version = 2.4.4

[bumpversion:file:.env]

Expand Down
2 changes: 1 addition & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fi

export PROJECT_NAME=$OPEN_PROJECT_NAME
export PROJECT_DIR="$PWD"
export PROJECT_VERSION="2.4.3"
export PROJECT_VERSION="2.4.4"

if [ ! -d "venv" ]; then
if ! hash pyvenv 2>/dev/null; then
Expand Down
2 changes: 2 additions & 0 deletions ACKNOWLEDGEMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Code Contributors
- Trevor Bekolay (@tbekolay)
- Elijah Wilson (@tizz98)
- Chelsea Dole (@chelseadole)
- Antti Kaihola (@akaihola)

Documenters
===================
Expand Down Expand Up @@ -75,6 +76,7 @@ Documenters
- Amanda Crosley (@waddlelikeaduck)
- Chelsea Dole (@chelseadole)
- Joshua Crowgey (@jcrowgey)
- Antti Kaihola (@akaihola)

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

Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ Ideally, within a virtual environment.
Changelog
=========

### 2.4.4 - March 21, 2019
- Added the ability to change the default output format for CLI endpoints both at the API and global level.
- Added the ablity to extend CLI APIs in addition to HTTP APIs issue #744.
- Added optional built-in API aware testing for CLI commands.
- Add unit test for `extend_api()` with CLI commands
- Fix running tests using `python setup.py test`
- Fix issue #749 extending API with mixed GET/POST methods
- Documented the `multiple_files` example
- Added the `--without-cython` option to `setup.py`

### 2.4.3 [hotfix] - March 17, 2019
- Fix issue #737 - latest hug release breaks meinheld worker setup

Expand Down
Empty file.
17 changes: 17 additions & 0 deletions examples/multi_file_cli/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import hug

import sub_api


@hug.cli()
def echo(text: hug.types.text):
return text


@hug.extend_api(sub_command='sub_api')
def extend_with():
return (sub_api, )


if __name__ == '__main__':
hug.API(__name__).cli()
6 changes: 6 additions & 0 deletions examples/multi_file_cli/sub_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import hug


@hug.cli()
def hello():
return 'Hello world'
9 changes: 9 additions & 0 deletions examples/multiple_files/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Splitting the API into multiple files

Example of an API defined in multiple Python modules and combined together
using the `extend_api()` helper.

Run with `hug -f api.py`. There are three API endpoints:
- `http://localhost:8000/``say_hi()` from `api.py`
- `http://localhost:8000/part1``part1()` from `part_1.py`
- `http://localhost:8000/part2``part2()` from `part_2.py`
7 changes: 7 additions & 0 deletions examples/multiple_files/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@

@hug.get('/')
def say_hi():
"""This view will be at the path ``/``"""
return "Hi from root"


@hug.extend_api()
def with_other_apis():
"""Join API endpoints from two other modules
These will be at ``/part1`` and ``/part2``, the paths being automatically
generated from function names.
"""
return [part_1, part_2]
1 change: 1 addition & 0 deletions examples/multiple_files/part_1.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

@hug.get()
def part1():
"""This view will be at the path ``/part1``"""
return 'part1'
1 change: 1 addition & 0 deletions examples/multiple_files/part_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

@hug.get()
def part2():
"""This view will be at the path ``/part2``"""
return 'Part 2'
6 changes: 3 additions & 3 deletions hug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
middleware, output_format, redirect, route, test, transform, types, use, validate)
from hug._version import current
from hug.api import API
from hug.decorators import (context_factory, default_input_format, default_output_format, delete_context, directive,
extend_api, middleware_class, reqresp_middleware, request_middleware, response_middleware,
startup, wraps)
from hug.decorators import (context_factory, default_input_format, default_output_format,
delete_context, directive, extend_api, middleware_class, reqresp_middleware,
request_middleware, response_middleware, startup, wraps)
from hug.route import (call, cli, connect, delete, exception, get, get_post, head, http, local,
not_found, object, options, patch, post, put, sink, static, trace)
from hug.types import create as type
Expand Down
2 changes: 1 addition & 1 deletion hug/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
"""
from __future__ import absolute_import

current = "2.4.3"
current = "2.4.4"
37 changes: 30 additions & 7 deletions hug/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
from wsgiref.simple_server import make_server

import falcon
from falcon import HTTP_METHODS

import hug.defaults
import hug.output_format
from falcon import HTTP_METHODS
from hug import introspect
from hug._async import asyncio, ensure_future
from hug._version import current
Expand Down Expand Up @@ -156,7 +157,7 @@ def add_exception_handler(self, exception_type, error_handler, versions=(None, )
placement = self._exception_handlers.setdefault(version, OrderedDict())
placement[exception_type] = (error_handler, ) + placement.get(exception_type, tuple())

def extend(self, http_api, route="", base_url=""):
def extend(self, http_api, route="", base_url="", **kwargs):
"""Adds handlers from a different Hug API to this one - to create a single API"""
self.versions.update(http_api.versions)
base_url = base_url or self.base_url
Expand All @@ -167,7 +168,7 @@ def extend(self, http_api, route="", base_url=""):
for method, versions in handler.items():
for version, function in versions.items():
function.interface.api = self.api
self.routes[base_url][route + item_route] = handler
self.routes[base_url].setdefault(route + item_route, {}).update(handler)

for sink_base_url, sinks in http_api.sinks.items():
for url, sink in sinks.items():
Expand Down Expand Up @@ -371,7 +372,7 @@ def error_serializer(request, response, error):

class CLIInterfaceAPI(InterfaceAPI):
"""Defines the CLI interface specific API"""
__slots__ = ('commands', 'error_exit_codes',)
__slots__ = ('commands', 'error_exit_codes', '_output_format')

def __init__(self, api, version='', error_exit_codes=False):
super().__init__(api)
Expand All @@ -396,6 +397,25 @@ def handlers(self):
"""Returns all registered handlers attached to this API"""
return self.commands.values()

def extend(self, cli_api, command_prefix="", sub_command="", **kwargs):
"""Extends this CLI api with the commands present in the provided cli_api object"""
if sub_command and command_prefix:
raise ValueError('It is not currently supported to provide both a command_prefix and sub_command')

if sub_command:
self.commands[sub_command] = cli_api
else:
for name, command in cli_api.commands.items():
self.commands["{}{}".format(command_prefix, name)] = command

@property
def output_format(self):
return getattr(self, '_output_format', hug.defaults.cli_output_format)

@output_format.setter
def output_format(self, formatter):
self._output_format = formatter

def __str__(self):
return "{0}\n\nAvailable Commands:{1}\n".format(self.api.doc or self.api.name,
"\n\n\t- " + "\n\t- ".join(self.commands.keys()))
Expand Down Expand Up @@ -497,12 +517,15 @@ def context(self):
self._context = {}
return self._context

def extend(self, api, route="", base_url=""):
def extend(self, api, route="", base_url="", http=True, cli=True, **kwargs):
"""Adds handlers from a different Hug API to this one - to create a single API"""
api = API(api)

if hasattr(api, '_http'):
self.http.extend(api.http, route, base_url)
if http and hasattr(api, '_http'):
self.http.extend(api.http, route, base_url, **kwargs)

if cli and hasattr(api, '_cli'):
self.cli.extend(api.cli, **kwargs)

for directive in getattr(api, '_directives', {}).values():
self.add_directive(directive)
Expand Down
16 changes: 11 additions & 5 deletions hug/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,21 @@
from hug.format import underscore


def default_output_format(content_type='application/json', apply_globally=False, api=None):
def default_output_format(content_type='application/json', apply_globally=False, api=None, cli=False, http=True):
"""A decorator that allows you to override the default output format for an API"""
def decorator(formatter):
formatter = hug.output_format.content_type(content_type)(formatter)
if apply_globally:
hug.defaults.output_format = formatter
if http:
hug.defaults.output_format = formatter
if cli:
hug.defaults.cli_output_format = formatter
else:
apply_to_api = hug.API(api) if api else hug.api.from_object(formatter)
apply_to_api.http.output_format = formatter
if http:
apply_to_api.http.output_format = formatter
if cli:
apply_to_api.cli.output_format = formatter
return formatter
return decorator

Expand Down Expand Up @@ -169,12 +175,12 @@ def decorator(middleware_class):
return decorator


def extend_api(route="", api=None, base_url=""):
def extend_api(route="", api=None, base_url="", **kwargs):
"""Extends the current api, with handlers from an imported api. Optionally provide a route that prefixes access"""
def decorator(extend_with):
apply_to_api = hug.API(api) if api else hug.api.from_object(extend_with)
for extended_api in extend_with():
apply_to_api.extend(extended_api, route, base_url)
apply_to_api.extend(extended_api, route, base_url, **kwargs)
return extend_with
return decorator

Expand Down
1 change: 1 addition & 0 deletions hug/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import hug

output_format = hug.output_format.json
cli_output_format = hug.output_format.text

input_format = {
'application/json': hug.input_format.json,
Expand Down
1 change: 1 addition & 0 deletions hug/input_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from urllib.parse import parse_qs as urlencoded_converter

from falcon.util.uri import parse_query_string

from hug.format import content_type, underscore
from hug.json_module import json as json_converter

Expand Down
11 changes: 3 additions & 8 deletions hug/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,9 @@ class CLI(Interface):

def __init__(self, route, function):
super().__init__(route, function)
if not self.outputs:
self.outputs = self.api.cli.output_format

self.interface.cli = self
self.reaffirm_types = {}
use_parameters = list(self.interface.parameters)
Expand Down Expand Up @@ -452,14 +455,6 @@ def exit(self, status=0, message=None):

self.api.cli.commands[route.get('name', self.interface.spec.__name__)] = self

@property
def outputs(self):
return getattr(self, '_outputs', hug.output_format.text)

@outputs.setter
def outputs(self, outputs):
self._outputs = outputs

def output(self, data, context):
"""Outputs the provided data using the transformations and output format specified for this CLI endpoint"""
if self.transform:
Expand Down
1 change: 1 addition & 0 deletions hug/output_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

import falcon
from falcon import HTTP_NOT_FOUND

from hug import introspect
from hug.format import camelcase, content_type
from hug.json_module import json as json_converter
Expand Down
Loading

0 comments on commit a9af206

Please sign in to comment.