Skip to content

Make the examples way better #35

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Feb 1, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f424317
add a ThreadPool-based performer for parallels. untested, broken
radix Jan 1, 2015
29ce4fa
working example using 'requests', while maintaining the twisted-based…
radix Jan 1, 2015
f2b5deb
further explode examples from each other.
radix Jan 2, 2015
d51b18c
docs for sync_main.py
radix Jan 2, 2015
ea569df
create a "github" directory in examples
radix Jan 2, 2015
450ab55
fix up docs a bit
radix Jan 2, 2015
a6839cb
oops, I accidentally duplicated the *_main.py scripts in multiple dir…
radix Jan 2, 2015
a461185
more tweaking of file structure in the GH example.
radix Jan 2, 2015
d7c5f5e
yet more restructuring
radix Jan 2, 2015
5cf849f
more reorg of examples. readline and http stuff factored out.
radix Jan 2, 2015
123822a
fix example tests
radix Jan 2, 2015
1843826
minor changes to examples README
radix Jan 2, 2015
4025c91
import statement organization
radix Jan 2, 2015
75af4c1
fix docstring syntax for perform_parallel_with_pool
radix Jan 2, 2015
b5da511
make the sync_main example work on Py3. Woohoo!
radix Jan 2, 2015
3fd6205
Merge branch 'generic-parallel' into gh-example-requests
radix Jan 23, 2015
c72c47c
thrashing around with the threaded parallel performer (some tests add…
radix Jan 23, 2015
8e90869
leave a comment indicating the test is bogus.
radix Jan 23, 2015
07c49fe
Merge branch 'generic-parallel' into gh-example-requests
radix Jan 24, 2015
e76d25a
make FirstError work for the threaded parallel performer
radix Jan 24, 2015
efe8c07
get rid of perform_parallel_with_pool from __init__.py's __all__
radix Jan 24, 2015
061396f
Merge branch 'master' into gh-example-requests
radix Feb 1, 2015
8ddba17
update fir FirstError changes, and also refactor unit tests so the co…
radix Feb 1, 2015
b4554f2
lint
radix Feb 1, 2015
ef959d3
Merge branch 'gh-example-requests' into gh-example-requests-2
radix Feb 1, 2015
8a7101f
fix import of perform_parallel_with_pool
radix Feb 1, 2015
7cf1163
Merge branch 'master' into gh-example-requests-2
radix Feb 1, 2015
7e27e1f
try to clean up formatting and add links in examples/README.md
radix Feb 1, 2015
a4de0a8
fix the command for running the example tests
radix Feb 1, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Effect Examples

## http

The `http` directory contains a very simple `HTTPRequest` intent and performers
using common HTTP client libraries:
[requests](http://warehouse.python.org/project/requests/) and
[treq](https://warehouse.python.org/project/treq/).


## readline_intent

The `readline_intent.py` file has a simple `ReadLine` intent that uses
`raw_input` (or `input` in Py3) to prompt the user for input.

## github

The `github` directory contains a simple application that lets the user input a
GitHub username and prints out a list of all repositories that that user has
access to. It depends on the `http` and `readline_intent` modules.

There are two entrypoints into the example:
[`examples.github.sync_main`](github/sync_main.py) and
[`examples.github.twisted_main`](github/twisted_main.py). `sync_main` does
typical blocking IO, and `twisted_main` uses asynchronous IO. Note that the
vast majority of the code doesn't need to care about this difference; the only
part that cares about it is the `*_main.py` files. All of the logic in
[`core.py`](github/core.py) is generic. Tests are in
[`test_core.py`](github/test_core.py).

To run them:

python -m examples.github.sync_main

or

python -m examples.github.twisted_main


Note that the twisted example does not run on Python 3, but all other examples
do.
Empty file added examples/github/__init__.py
Empty file.
69 changes: 69 additions & 0 deletions examples/github/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""
Core application / API interaction logic of the GitHub example.

None of this code needs to change based on your I/O strategy -- you can
use blocking API (e.g. the ``requests`` library) or Twisted, asyncio,
or Tornado implementations of an HTTP client with this code, by providing
different performers for the :obj:`HTTPRequest` intent.
"""

from __future__ import print_function

from functools import reduce
import json
import operator

from effect import Effect, parallel

from ..readline_intent import ReadLine
from ..http.http_intent import HTTPRequest


def get_orgs(name):
"""
Fetch the organizations a user belongs to.

:return: An Effect resulting in a list of strings naming the user's
organizations.
"""
req = Effect(
HTTPRequest("get",
"https://api.github.com/users/{0}/orgs".format(name)))
return req.on(success=lambda x: [org['login'] for org in json.loads(x)])


def get_org_repos(name):
"""
Fetch the repos that belong to an organization.

:return: An Effect resulting in a list of strings naming the repositories.
"""
req = Effect(
HTTPRequest("get",
"https://api.github.com/orgs/{0}/repos".format(name)))
return req.on(success=lambda x: [repo['name'] for repo in json.loads(x)])


def get_orgs_repos(name):
"""
Fetch ALL of the repos that a user has access to, in any organization.

:return: An Effect resulting in a list of repositories.
"""
req = get_orgs(name)
req = req.on(lambda org_names: parallel(map(get_org_repos, org_names)))
req = req.on(lambda repo_lists: reduce(operator.add, repo_lists))
return req


def main_effect():
"""
Request a username from the keyboard, and look up all repos in all of
that user's organizations.

:return: an Effect resulting in a list of repositories.
"""
intent = ReadLine("Enter Github Username> ")
read_eff = Effect(intent)
org_repos_eff = read_eff.on(success=get_orgs_repos)
return org_repos_eff
62 changes: 62 additions & 0 deletions examples/github/sync_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Run this example with:
python -m examples.github.sync_main

This is an example of using Effect in a normal program that uses
synchronous/blocking functions to do I/O.

This code has these responsibilities:

- set up a dispatcher that knows how to find performers for all intents
used in this application. The application uses ReadLine, HTTPRequest, and
ParallelEffects.
- use :func:`effect.sync_perform` to perform an effect, which returns the
result of the effect (or raises an exception it it failed).
"""

from __future__ import print_function

from functools import partial
from multiprocessing.pool import ThreadPool

from effect import (
ComposedDispatcher,
ParallelEffects,
TypeDispatcher,
sync_perform)
from effect.threads import perform_parallel_with_pool


from ..http.http_intent import HTTPRequest
from ..http.sync_http import perform_request_requests
from ..readline_intent import ReadLine, perform_readline_stdin

from .core import main_effect


def get_dispatcher():
"""
Create a dispatcher that can find performers for :obj:`ReadLine`,
:obj:`HTTPRequest`, and :obj:`ParallelEffects`. There's a built-in
performer for ParallelEffects that uses a multiprocessing ThreadPool,
:func:`effect.perform_parallel_with_pool`.
"""
my_pool = ThreadPool()
pool_performer = partial(perform_parallel_with_pool, my_pool)
return ComposedDispatcher([
TypeDispatcher({
ReadLine: perform_readline_stdin,
HTTPRequest: perform_request_requests,
ParallelEffects: pool_performer,
})
])


def main():
dispatcher = get_dispatcher()
eff = main_effect()
print(sync_perform(dispatcher, eff))


if __name__ == '__main__':
main()
17 changes: 9 additions & 8 deletions examples/test_github_example.py → examples/github/test_core.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Run these tests with "trial examples.test_github_example"
# or "python -m testtools.run examples.test_github_example"
# Run these tests with "trial examples.github.test_core"
# or "python -m testtools.run examples.github.test_core"

import json

from testtools import TestCase

from effect import ParallelEffects
from effect.testing import resolve_effect
from . import github_example

from .core import get_orgs, get_org_repos, get_orgs_repos


class GithubTests(TestCase):
Expand All @@ -16,14 +17,14 @@ def test_get_orgs_request(self):
get_orgs returns an effect that makes an HTTP request to
the GitHub API to look up organizations for a user.
"""
eff = github_example.get_orgs('radix')
eff = get_orgs('radix')
http = eff.intent
self.assertEqual(http.method, 'get')
self.assertEqual(http.url, 'https://api.github.com/users/radix/orgs')

def test_get_orgs_success(self):
"""get_orgs extracts the result into a simple list of orgs."""
eff = github_example.get_orgs('radix')
eff = get_orgs('radix')
self.assertEqual(
resolve_effect(eff, json.dumps([{'login': 'twisted'},
{'login': 'rackerlabs'}])),
Expand All @@ -34,14 +35,14 @@ def test_get_org_repos_request(self):
get_org_repos returns an effect that makes an HTTP request to
the GitHub API to look up repos in an org.
"""
eff = github_example.get_org_repos('twisted')
eff = get_org_repos('twisted')
http = eff.intent
self.assertEqual(http.method, 'get')
self.assertEqual(http.url, 'https://api.github.com/orgs/twisted/repos')

def test_get_org_repos_success(self):
"""get_org_repos extracts the result into a simple list of repos."""
eff = github_example.get_org_repos('radix')
eff = get_org_repos('radix')
self.assertEqual(
resolve_effect(eff, json.dumps([{'name': 'twisted'},
{'name': 'txstuff'}])),
Expand All @@ -53,7 +54,7 @@ def test_get_orgs_repos(self):
a user, and then looks up all of the repositories of those orgs in
parallel, and returns a single flat list of all repos.
"""
effect = github_example.get_orgs_repos('radix')
effect = get_orgs_repos('radix')
self.assertEqual(effect.intent.method, 'get')
self.assertEqual(effect.intent.url,
'https://api.github.com/users/radix/orgs')
Expand Down
59 changes: 59 additions & 0 deletions examples/github/twisted_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""
Run this example with:
python -m examples.github.twisted_main

This is an example of using Effect with Twisted.

It's important to note that none of the application code relies on Twisted --
this is the only file that has any dependencies on Twisted. There's also an
example of running the same application code in ``sync_main.py`` in the same
directory.

This code has these responsibilities:

- set up a dispatcher that knows how to find performers for all intents
used in this application. The application uses ReadLine, HTTPRequest, and
ParallelEffects.
- use :func:`effect.twisted.perform` to perform an effect, which returns a
Deferred that we can return from our react function.
"""

from __future__ import print_function

from twisted.internet.task import react

from effect.twisted import make_twisted_dispatcher, perform
from effect import (
ComposedDispatcher,
TypeDispatcher)

from ..http.http_intent import HTTPRequest
from ..http.twisted_http import perform_request_with_treq
from ..readline_intent import ReadLine, perform_readline_stdin

from .core import main_effect


def get_dispatcher(reactor):
"""
Create a dispatcher that can find performers for :obj:`ReadLine`,
:obj:`HTTPRequest`, and :obj:`ParallelEffects`.
:func:`make_twisted_dispatcher` is able to provide the ``ParallelEffects``
performer, so we compose it with our own custom :obj:`TypeDispatcher`.
"""
return ComposedDispatcher([
TypeDispatcher({
ReadLine: perform_readline_stdin,
HTTPRequest: perform_request_with_treq,
}),
make_twisted_dispatcher(reactor),
])


def main(reactor):
dispatcher = get_dispatcher(reactor)
eff = main_effect()
return perform(dispatcher, eff).addCallback(print)

if __name__ == '__main__':
react(main, [])
85 changes: 0 additions & 85 deletions examples/github_example.py

This file was deleted.

Empty file added examples/http/__init__.py
Empty file.
14 changes: 14 additions & 0 deletions examples/http/http_intent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class HTTPRequest(object):
"""
An HTTP request intent.
"""

def __init__(self, method, url, headers=None, data=None):
self.method = method
self.url = url
self.headers = headers
self.data = data

def __repr__(self):
return "HTTPRequest(%r, %r, headers=%r, data=%r)" % (
self.method, self.url, self.headers, self.data)
Loading