Skip to content

Commit

Permalink
Merge branch 'sloria-docs-fixes' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
timothycrosley committed Mar 19, 2016
2 parents 2825e58 + a945eeb commit 9eff2c6
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 34 deletions.
26 changes: 16 additions & 10 deletions ARCHITECTURE.md
@@ -1,10 +1,10 @@
The guiding thought behind the architecture
===================
===========================================
hug is the cleanest way to create HTTP REST APIs on Python3.
It consistently benchmarks among the top 3 performing web frameworks for Python, handily beating out Flask and Django.
For almost every common Web API task the code written to accomplish it in hug is a small fraction of what is required in other Frameworks.

However, it's important to note, hug is not a Web API Framework. OK -- that certainly is a function it performs. And exceptionally well at that.
However, it's important to note, hug is not a Web API Framework. OK--that certainly is a function it performs. And exceptionally well at that.
But at its core, hug is a framework for exposing idiomatically correct and standard internal Python APIs externally.
A framework to allow developers and architects to define logic and structure once, and then cleanly expose it over other means.

Expand All @@ -19,7 +19,8 @@ This central concept also frees hug to rely on the fastest and best of breed com


What this looks like in practice - an illustrative example
===================
===========================================

Let's say I have a very simple Python API I've built to add 2 numbers together. I call my invention `addition`.
Trust me, this is legit. It's trademarked and everything:

Expand Down Expand Up @@ -80,20 +81,21 @@ All my original unit tests continue to pass and my code coverage remains at 100%
It turns out, the problems and thoughts that go into defining a clean well documented API for internal use greatly mirror those that are required to expose an API for external use. hug recognizes this and enables cleanly reusing the documentation, requirements, and structure of internal APIs for external use. This also encourages easier to use and well documented internal APIs: a major win/win.

What happened internally as I exposed my API to new interfaces?
===================
===========================================

A few things happen when you wrapped that first function for external use, with hug.cli():

- hug created a singleton hug.API object on your module to keep track of all interfaces that exist within the module
- This is referable by `__hug__` or `hug.API(__name__)`
- a new hug.interface.CLI() object was created and attached to `add.interface.cli`
- a new `hug.interface.CLI()` object was created and attached to `add.interface.cli`
- This interface fully encapsulates the logic needed to expose `add` as a command line tool
- NOTE: all supported ways to expose a function via hug can be found in `hug/interface.py`
- the original Python `add` function is returned unmodified (with exception to the `.interface.cli` property addition)

Then when I extended my API to run as HTTP service the same basic pattern was followed:

- hug saw that the singleton already existed
- a new hug.interface.HTTP() object was created and attached to `add.interface.http`
- a new `hug.interface.HTTP()` object was created and attached to `add.interface.http`
- This interface encapsulates the logic needed to expose the `add` command as an HTTP service
- The new HTTP interface handler is registered to the API singleton
- the original Python `add` function is returned unmodified (with exception to the `.interface.http` property addition)
Expand All @@ -109,7 +111,8 @@ When I run `hug -f add.py` the hug runner looks for the
It then uses this new Falcon API to directly handle incoming HTTP requests.

Where does the code live for these core pieces?
===================
===========================================

While hug has a lot of modules that enable it to provide a great depth of functionality, everything accomplished above,
and that is core to hug, lives in only a few:

Expand All @@ -126,7 +129,8 @@ Beyond these there is one additional internal utility library that enables hug t
This module provides utility functions that enable hugs routers to determine what arguments a function takes and in what form.

Enabling interfaces to improve upon internal functions
===================
===========================================

hug provides several mechanisms to enable your exposed interfaces to have additional capabilities not defined by
the base Python function.

Expand Down Expand Up @@ -154,15 +158,17 @@ the base Python function.
- The default assumption for output_formatting is JSON

Switching from using a hug API over one interface to another
===================
===========================================

hug does its best to also solve the other side of the coin: that is how APIs are used.
While native Python will always be the fastest, HTTP can provide attractive auto updating
and clear responsibility separation benefits. You can interact with hug APIs via hug.use.[interface] if the ability
to switch between these is a high priority for you. The code that enables this is found in `hug/use.py` and should be
kept in mind if working on adding an additional interface for hug, or changing how hug calls functions.

Feel free to update or request more info :)
===================
===========================================

I tried my best to highlight where important functionality in the hug project lives via this Architecture document, as well as
explain the reasoning behind it. However, this document is certainly not complete! If you encounter anything you would like to be
expanded upon or explained in detail here, please either let me know or modify the document so everyone can get a good walkthrough of hug's architecture.
Expand Down
32 changes: 16 additions & 16 deletions documentation/ROUTING.md
@@ -1,5 +1,5 @@
Routing in hug
===================
==============

The most basic function of any framework meant to enable external interaction with an API, is routing how the external
interaction will correspond to internal function calls and business logic. hug provides flexible and powerful routers
Expand All @@ -15,7 +15,7 @@ And, while hug uses functions in most of its examples, it supports applying rout
and enable automatic argument supplying via directives.

Using a router as a decorator
===================
=============================

The most basic use case is to simply define the route directly above the function you need to expose as a decorator:

Expand All @@ -29,7 +29,7 @@ The most basic use case is to simply define the route directly above the functio
This is clear, explicit, and obvious. As such, this is recommended for most basic APIs.

Declaring a router separate from a function
===================
===========================================

Sometimes, in more complex use-cases, it's necessary to define routing separate from where the code itself is defined.
hug aims to make this as easy and intuitive as it can be:
Expand Down Expand Up @@ -63,7 +63,7 @@ Or, alternatively:
hug.get('/home', api=api)(external.root)

Chaining routers for easy re-use
===================
================================

A very common scenerio when using hug routers, because they are so powerful, is duplication between routers.
For instance: if you decide you want every route to return the 404 page when a validation error occurs or you want to
Expand All @@ -90,7 +90,7 @@ as a method on the existing router. Then you pass in any additional parameters y
shown in the math example above.

Common router parameters
===================
========================

There are a few parameters that are shared between all router types, as they are globaly applicable to all currently supported interfaces:

Expand All @@ -100,9 +100,9 @@ There are a few parameters that are shared between all router types, as they are
- `requires`: A list or single function that must all return `True` for the function to execute when called via this interface (commonly used for authentication)

HTTP Routers
===================
============

in addition to `hug.http` hug includes convience decorators for all common HTTP METHODS (`hug.connect`, `hug.delete`, `hug.get`, `hug.head`, `hug.options`, `hug.patch`, `hug.post`, `hug.put`, `hug.get_post` and `hug.trace`). These methods are functionally the same as calling `@hug.http(accept=(METHOD, )) and are otherwise identical to the http router.
in addition to `hug.http` hug includes convience decorators for all common HTTP METHODS (`hug.connect`, `hug.delete`, `hug.get`, `hug.head`, `hug.options`, `hug.patch`, `hug.post`, `hug.put`, `hug.get_post` and `hug.trace`). These methods are functionally the same as calling `@hug.http(accept=(METHOD, ))` and are otherwise identical to the http router.

- `urls`: A list of or a single URL that should be routed to the function. Supports defining variables withing the URL that will automatically be passed to the function when `{}` notation is found in the URL: `/website/{page}`. Defaults to the name of the function being routed to.
- `accept`: A list of or a single HTTP METHOD value to accept. Defaults to all common HTTP methods.
Expand All @@ -119,25 +119,25 @@ in addition to `hug.http` hug includes convience decorators for all common HTTP


CLI Routing
===================
===========

Any endpoint can also be exposed to the command line as well, using `@hug.cli`:

- `name`: The name that should execute the command from the command line. Defaults to the name of the function being routed
- `version`: The optional version associated with this command line application
- `name`: The name that should execute the command from the command line. Defaults to the name of the function being routed.
- `version`: The optional version associated with this command line application.
- `doc`: Documentation to provide to users of this command line tool. Defaults to the functions doc string.


Local Routing
===================
=============

By default all hug APIs are already valid local APIs. However, sometimes it can be useful to apply type annotations and/or directives to local use as well. For these cases hug provides `@hug.local`:

- `validate`: Apply type anntations to local use of the function. Defaults to `True`
- `directives`: Apply directives to local use of the function. Defaults to `True`
- `version`: Specify a version of the API for local use. If versions are being used, this generally should be the latest supported
- `validate`: Apply type annotations to local use of the function. Defaults to `True`.
- `directives`: Apply directives to local use of the function. Defaults to `True`.
- `version`: Specify a version of the API for local use. If versions are being used, this generally should be the latest supported.
- `on_invalid`: A transformation function to run outputed data through, only if the request fails validation. Defaults to the endpoints specified general transform function, can be set to not run at all by setting to `None`.
- `output_invalid`: Specifies an output format to attach to the endpoint only on the case that validation fails. Defaults to the endpoints specified output format.
- `raise_on_invalid`: If set to true, instead of collecting validation errors in a dictionry, hug will simply raise them as they occur.
- `raise_on_invalid`: If set to `True`, instead of collecting validation errors in a dictionry, hug will simply raise them as they occur.

NOTE: unlike all other routers, this does modify the function in place
NOTE: unlike all other routers, this modifies the function in-place
16 changes: 8 additions & 8 deletions documentation/TYPE_ANNOTATIONS.md
@@ -1,11 +1,11 @@
Type annotations in hug
===================
=======================

hug leverages Python3 type annotations for validation and API specification. Within the context of hug, annotations should be set to one of 4 things:

- A cast function, built-in, or your own (str, int, etc) that takes a value casts it and then returns it, raising an exception if it is not in a format that can be cast into the desired type
- A hug type (hug.types.text, hug.types.number, etc.). These are essentially built-in cast functions that provide more contextual information, and good default error messages
- A Marshmallow type and/or schema. In hug 2.0.0 Marshmallow is a first class citizen in hug, and all fields and schemas defined in this framework can be used in hug directly as type annotations
- A [marshmallow](https://marshmallow.readthedocs.org/en/latest/) type and/or schema. In hug 2.0.0 Marshmallow is a first class citizen in hug, and all fields and schemas defined with it can be used in hug as type annotations
- A string. When a basic Python string is set as the type annotation it is used by hug to generate documentation, but does not get applied during the validation phase

For example:
Expand All @@ -17,14 +17,14 @@ For example:
def hello(first_name: hug.types.text, last_name: 'Family Name', age: int):
print("Hi {0} {1}!".format(first_name, last_name)

Is a valid hug endpoint.
is a valid hug endpoint.

Anytime a type annotation raises an exception during casting of a type it is seen as a failure, otherwise the cast is assumed succesfull with the returned type replacing the passed in parameter. By default all errors are collected in an errors dictionary and returned as the output of the endpoint before the routed function ever gets called. To change how errors are returned you can transform them via the `on_invalid` route option, and specify a specific output format for errors by specifying the `output_invalid` route option. Or, if you prefer, you can keep hug from handling the validation errors at all by passing in `raise_on_invalid=True` to the route.
Any time a type annotation raises an exception during casting of a type, it is seen as a failure. Otherwise the cast is assumed successful with the returned type replacing the passed-in parameter. By default, all errors are collected in an errors dictionary and returned as the output of the endpoint before the routed function ever gets called. To change how errors are returned you can transform them via the `on_invalid` route option, and specify a specific output format for errors by specifying the `output_invalid` route option. Or, if you prefer, you can keep hug from handling the validation errors at all by passing in `raise_on_invalid=True` to the route.

Built in hug types
===================
==================

hug provides several built in types for common API use cases:
hug provides several built-in types for common API use cases:

- `number`: Validates that a whole number was passed in
- `float_number`: Validates that a valid floating point number was passed in
Expand All @@ -44,10 +44,10 @@ hug provides several built in types for common API use cases:
- `length(lower, upper, convert=text)`: Accepts a a value that is withing a specific length limit
- `shorter_than(limit, convert=text)`: Accepts a text value shorter than the specified length limit
- `longer_than(limit, convert=text)`: Accepts a value up to the specified limit
- `cutt_off(limit, convert=text)`: Cuts off the provided value at the specified index
- `cut_off(limit, convert=text)`: Cuts off the provided value at the specified index

Extending and creating new hug types
===================
====================================

The most obvious way to extend a hug type is to simply inherit from the base type defined in `hug.types` and then override `__call__` to override how the cast function, or override `__init__` to override what parameters the type takes:

Expand Down

0 comments on commit 9eff2c6

Please sign in to comment.