Full Code of kespindler/albatross for AI

master 35b9a7a370eb cached
26 files
69.6 KB
34.9k tokens
115 symbols
1 requests
Download .txt
Repository: kespindler/albatross
Branch: master
Commit: 35b9a7a370eb
Files: 26
Total size: 69.6 KB

Directory structure:
gitextract_m4qwufff/

├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── albatross/
│   ├── __init__.py
│   ├── compat.py
│   ├── data_types.py
│   ├── http_error.py
│   ├── request.py
│   ├── response.py
│   ├── server.py
│   └── status_codes.py
├── bench/
│   ├── benchmark.sh
│   ├── client.py
│   ├── data.txt
│   ├── run_aiohttp.py
│   ├── run_albatross.py
│   ├── run_flask.py
│   └── run_tornado.py
├── examples/
│   └── basic.py
├── setup.py
└── tests/
    ├── __init__.py
    ├── test_data_types.py
    ├── test_request.py
    ├── test_response.py
    └── test_server.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
__pycache__
dist
albatross3.egg-info/
build/
.DS_Store
.coverage
cover
bench/output


================================================
FILE: .travis.yml
================================================
language: python
python:
    - "3.5"
install: 
    - pip install aiohttp ujson
    - python3 setup.py install
script: nosetests


================================================
FILE: Makefile
================================================
COVERAGE=98
TEST=nosetests -s --with-coverage --cover-package=albatross --cover-branches $(TARGET)

upload:
	git push
	python3 setup.py sdist bdist_wheel upload

test:
	$(TEST) --cover-min-percentage $(COVERAGE)

cover:
	$(TEST) --cover-html
	which open && open cover/index.html

.PHONY: cover


================================================
FILE: README.md
================================================
[![Build Status](https://travis-ci.org/kespindler/albatross.svg?branch=master)](https://travis-ci.org/kespindler/albatross)

# Albatross

A modern, fast, simple, natively-async web framework. (Python3.5 only)

```python
from albatross import Server
import asyncio


class Handler:
    async def on_get(self, req, res):
        await asyncio.sleep(0.1)
        res.write('Hello, %s' % req.args['name'])


app = Server()
app.add_route('/{name})', Handler())
app.serve()
```

### Notes for Usage

For now (pre 1.0.0), I'm making no claims about API stability (but will try to avoid changes). That said,
I'm using this framework for some small projects, and it is a joy to work in!
Reach out if you want to use this, as I'm happy to incorporate your feedback!

## Install

    pip3 install albatross3

## Features

- You can read the entire codebase in about 30 minutes.

- It's natively async. Doing `await` database calls or controller calls in your views just works!

- This works with the `uvloop` project, to make your server fast!

## Benchmarks

- My benchmarks indicate that albatross is as fast as aiohttp, both of which are twice as fast as
  tornado. You can run the benchmarks by poking around in the `bench/` folder.



================================================
FILE: albatross/__init__.py
================================================
from albatross.request import Request
from albatross.response import Response
from albatross.server import Server

from albatross.status_codes import *
from albatross.http_error import HTTPError


================================================
FILE: albatross/compat.py
================================================
try:
    import ujson as json
except ImportError:
    import json


================================================
FILE: albatross/data_types.py
================================================
from albatross.http_error import HTTPError
from albatross.status_codes import HTTP_400


def caseless_pairs(seq):
    for k, v in seq:
        yield k.lower(), v


class Immutable:
    def __setitem__(self, k, v):
        raise TypeError('Cannot set item on %s' % self.__class__)

    def update(self, E=None, **F):
        raise TypeError('Cannot update on %s' % self.__class__)


class ImmutableMultiDict(Immutable, dict):
    def __getitem__(self, k):
        if k in self:
            return super(ImmutableMultiDict, self).__getitem__(k)[0]
        raise HTTPError(HTTP_400, 'Must provide parameter \'%s\'.' % k)

    def get(self, k, d=None):
        if k in self:
            return super(ImmutableMultiDict, self).__getitem__(k)[0]
        return d

    def get_all(self, k, d=None):
        if k in self:
            return super(ImmutableMultiDict, self).__getitem__(k)
        return d


class CaselessDict(dict):
    def __init__(self, it=None, **kwargs):
        it = caseless_pairs(it) if it else []
        if kwargs:
            kwargs = {k.lower(): v for k, v in kwargs.items()}
        super(CaselessDict, self).__init__(it, **kwargs)

    def __contains__(self, k):
        return super(CaselessDict, self).__contains__(k.lower())

    def __getitem__(self, k):
        return super(CaselessDict, self).__getitem__(k.lower())

    def __iter__(self):
        for k in super(CaselessDict, self).__iter__():
            yield k.lower()

    def __setitem__(self, k, v):
        super(CaselessDict, self).__setitem__(k.lower(), v)

    def get(self, k, d=None):
        if k in self:
            return super(CaselessDict, self).__getitem__(k.lower())
        return d

    def update(self, other=None, **kwargs):
        updates = {k.lower(): v for k, v in kwargs.items()}
        if other:
            if hasattr(other, 'items'):
                other = other.items()
            updates.update(caseless_pairs(other))
        return super(CaselessDict, self).update(updates)


class ImmutableCaselessDict(Immutable, CaselessDict):
    pass


class ImmutableCaselessMultiDict(ImmutableMultiDict, CaselessDict):
    def __init__(self, it=None, **kwargs):
        it = caseless_pairs(it) if it else []
        if kwargs:
            kwargs = {k.lower(): [v] for k, v in kwargs.items()}
        for k, v in it:
            if k in kwargs:
                kwargs[k].append(v)
            else:
                kwargs[k] = [v]
        super(ImmutableCaselessMultiDict, self).__init__(**kwargs)


================================================
FILE: albatross/http_error.py
================================================


class HTTPError(Exception):
    def __init__(self, code, message=None):
        self.status_code = code
        self.message = message or code


================================================
FILE: albatross/request.py
================================================
from albatross.data_types import (
    ImmutableMultiDict,
    ImmutableCaselessMultiDict
)
from albatross.compat import json
import urllib.parse as parse
from httptools import parse_url
import cgi
import io


REQUEST_STATE_PROCESSING = 0
REQUEST_STATE_CONTINUE = 1
REQUEST_STATE_COMPLETE = 2


def trim_keys(d):
    return {k.strip(): v for k, v in d.items()}


class FileStorage:

    def __init__(self, field_storage):
        self.filename = field_storage.filename
        self.value = field_storage.value


class Request:
    """
    Attributes:
        method (str): One of GET, POST, PUT, DELETE
        path (str): Full request path
        query_string (str): Full query string
        query (dict): dict of the request query
        body (str): Request body
        args (dict): Dictionary of named parameters in route regex
        form (dict): Dictionary of body parameters
    """

    def __init__(self, method=None, path=None, query_string='',
                 args=None, headers=None, form=None, cookies=None):
        self._header_list = []
        self._state = REQUEST_STATE_PROCESSING
        self.method = method
        self.path = path
        self.query_string = query_string
        self.query = None
        self.args = args
        self.headers = ImmutableCaselessMultiDict()
        self.cookies = ImmutableMultiDict()
        self.raw_body = io.BytesIO()
        self.form = form

        if query_string:
            self.query = ImmutableMultiDict(parse.parse_qs(self.query_string))

        if headers:
            self.headers = ImmutableCaselessMultiDict(**headers)

        if cookies:
            self.cookies = ImmutableMultiDict(**cookies)

    def _parse_cookie(self, value):
        cookies = trim_keys(parse.parse_qs(value))
        return ImmutableMultiDict(cookies)

    def _parse_form(self, body_stream):
        # TODO theres probably a way to not read whole body first)
        env = {'REQUEST_METHOD': 'POST'}
        form = cgi.FieldStorage(body_stream, headers=self.headers, environ=env)
        d = {}
        for k in form.keys():
            if form[k].filename:
                d[k] = [FileStorage(form[k])]
            else:
                d[k] = [form[k].value]
        return ImmutableMultiDict(d)

    def _parse_body(self, body_stream):
        content_type = self.headers.get('Content-Type', '')
        if content_type == 'application/json':
            data = body_stream.getvalue().decode()
            self.form = json.loads(data)
        elif content_type.startswith('multipart/form-data'):
            self.form = self._parse_form(body_stream)
        elif content_type == 'application/x-www-form-urlencoded':
            data = body_stream.getvalue().decode()
            self.form = ImmutableMultiDict(parse.parse_qs(data))
        body_stream.seek(0)

    # HTTPRequestParser protocol methods
    def on_url(self, url: bytes):
        parsed = parse_url(url)
        self.path = parsed.path.decode()
        self.query_string = (parsed.query or b'').decode()
        self.query = ImmutableMultiDict(parse.parse_qs(self.query_string))

    def on_header(self, name: bytes, value: bytes):
        self._header_list.append((name.decode(), value.decode()))
        if name.lower() == b'expect' and value == b'100-continue':
            self._state = REQUEST_STATE_CONTINUE

    def on_headers_complete(self):
        self.headers = ImmutableCaselessMultiDict(self._header_list)
        cookie_value = self.headers.get('Cookie')
        if cookie_value:
            self.cookies = self._parse_cookie(cookie_value)

    def on_body(self, body: bytes):
        self.raw_body.write(body)

    def on_message_complete(self):
        self._state = REQUEST_STATE_COMPLETE
        self.raw_body.seek(0)
        self._parse_body(self.raw_body)

    @property
    def finished(self):
        return self._state == REQUEST_STATE_COMPLETE

    @property
    def needs_write_continue(self):
        return self._state == REQUEST_STATE_CONTINUE

    def reset_state(self):
        self._state = REQUEST_STATE_PROCESSING


================================================
FILE: albatross/response.py
================================================
from albatross import status_codes
from albatross.compat import json
from albatross.data_types import CaselessDict
from albatross.http_error import HTTPError


class Response:
    """
    Attributes:
        status_code (str): HTTP status code
        headers (dict): Be careful about case-sensitivity here.
        body (str):

    """

    def __init__(self):
        self.status_code = status_codes.HTTP_200
        self._chunks = []
        self.headers = CaselessDict([
            ('Content-Type', 'text/html')
        ])
        self.cookies = {}

    def clear(self):
        self._chunks = []

    def write(self, string):
        self._chunks.append(string.encode())

    def write_bytes(self, bytes):
        self._chunks.append(bytes)

    def write_json(self, data):
        self.headers['Content-Type'] = 'application/json'
        self._chunks.append(json.dumps(data).encode())

    def redirect(self, location, permanent=False):
        self.headers['Location'] = location
        if permanent:
            self.status_code = status_codes.HTTP_301
        else:
            self.status_code = status_codes.HTTP_302
        raise HTTPError(self.status_code)


================================================
FILE: albatross/server.py
================================================
import re
import asyncio
from datetime import datetime
from albatross import Request, Response
from albatross.status_codes import HTTP_404, HTTP_500, HTTP_405
from albatross.http_error import HTTPError
from httptools import HttpRequestParser
import traceback


def write_cookie(writer, key, value):
    if isinstance(value, tuple):
        value, duration = value
        if isinstance(duration, datetime):
            format = duration.strftime('%a %d %b %Y %H:%M:%S GMT').encode()
            writer.write(b'Set-Cookie: %s=%s;expires=%s\r\n' % (
                key.encode(), str(value).encode(), format)
            )
        elif isinstance(duration, int):
            writer.write(b'Set-Cookie: %s=%s;max-age=%d\r\n' % (
                key.encode(), str(value).encode(), duration)
            )
    else:
        writer.write(b'Set-Cookie: %s=%s\r\n' % (
            key.encode(), str(value).encode())
        )


class Server:
    """The core albatross server

    Attributes:
        _handlers (list): a list of route-handler tuples
        _middleware (list): a list of middlewares to process requests
    """
    def __init__(self):
        self._handlers = []
        self._middleware = []
        self.spoof_options = True

    def get_handler(self, path):
        for route, handler in self._handlers:
            match = route.match(path)
            if match:
                return handler, match.groupdict()
        return None, None

    def add_regex_route(self, route, handler):
        route += '$'
        compiled = re.compile(route)
        self._handlers.append((compiled, handler))

    def add_route(self, route, handler):
        route = re.sub('{([-_a-zA-Z]+)}', '(?P<\g<1>>[^/?]+)', route)
        self.add_regex_route(route, handler)

    def add_middleware(self, middleware):
        self._middleware.append(middleware)

    async def _parse_request(self, request_reader, response_writer):
        limit = 2 ** 16
        req = Request()
        parser = HttpRequestParser(req)

        while True:
            data = await request_reader.read(limit)
            parser.feed_data(data)
            if req.finished or not data:
                break
            elif req.needs_write_continue:
                response_writer.write(b'HTTP/1.1 100 (Continue)\r\n\r\n')
                req.reset_state()

        if req.path is None:
            # connected without a formed HTTP request
            return

        handler, args = self.get_handler(req.path)

        req.method = parser.get_method().decode().upper()
        req.args = args
        return req, handler

    async def _route_request(self, handler, req, res):
        method = req.method
        if handler is None:
            raise HTTPError(HTTP_404)
        elif method == 'GET' and hasattr(handler, 'on_get'):
            await handler.on_get(req, res)
        elif method == 'POST' and hasattr(handler, 'on_post'):
            await handler.on_post(req, res)
        elif method == 'PUT' and hasattr(handler, 'on_put'):
            await handler.on_put(req, res)
        elif method == 'DELETE' and hasattr(handler, 'on_delete'):
            await handler.on_delete(req, res)
        elif method == 'OPTIONS':
            if hasattr(handler, 'on_options'):
                await handler.on_options(req, res)
            elif self.spoof_options:
                res.headers['Allow'] = 'GET,POST,DELETE,PUT'
            else:
                raise HTTPError(HTTP_405)
        else:
            raise HTTPError(HTTP_405)

    async def _handle(self, request_reader, response_writer):
        """Takes reader and writer from asyncio loop server and
        writes the response to the request.

        :param request_reader:
        :param response_writer:
        :return:
        """
        res = Response()

        try:
            req, handler = await self._parse_request(
                request_reader, response_writer)

            for middleware in self._middleware:
                await middleware.process_request(req, res, handler)

            try:
                await self._route_request(handler, req, res)
            except HTTPError as e:
                self.handle_error(res, e)

            for middleware in self._middleware:
                await middleware.process_response(req, res, handler)
        except Exception as e:
            self.handle_error(res, e)

        self._write_response(res, response_writer)
        await response_writer.drain()
        response_writer.close()

    def handle_error(self, res, e):
        res.clear()
        if isinstance(e, HTTPError):
            res.status_code = e.status_code
            res.write(e.message)
        else:
            res.status_code = HTTP_500
            res.write(res.status_code)
        traceback.print_exc()

    def _write_response(self, res, writer):
        writer.write(b'HTTP/1.1 %s\r\n' % res.status_code.encode())
        if 'Content-Length' not in res.headers:
            length = sum(len(x) for x in res._chunks)
            res.headers['Content-Length'] = str(length)
        for key, value in res.headers.items():
            writer.write(key.encode() + b': ' + str(value).encode() + b'\r\n')
        for key, value in res.cookies.items():
            write_cookie(writer, key, value)
        writer.write(b'\r\n')
        for chunk in res._chunks:
            writer.write(chunk)
        writer.write_eof()

    async def initialize(self):
        pass

    def serve(self, port=8000, host='0.0.0.0'):
        loop = asyncio.get_event_loop()
        loop.run_until_complete(self.initialize())
        print('Serving on %s:%d' % (host, port))
        loop.create_task(asyncio.start_server(self._handle, host, port))
        loop.run_forever()
        loop.close()


================================================
FILE: albatross/status_codes.py
================================================
# Copied from https://github.com/falconry/falcon/blob/master/falcon/status_codes.py
HTTP_100 = '100 Continue'
HTTP_CONTINUE = HTTP_100
HTTP_101 = '101 Switching Protocols'
HTTP_SWITCHING_PROTOCOLS = HTTP_101

HTTP_200 = '200 OK'
HTTP_OK = HTTP_200
HTTP_201 = '201 Created'
HTTP_CREATED = HTTP_201
HTTP_202 = '202 Accepted'
HTTP_ACCEPTED = HTTP_202
HTTP_203 = '203 Non-Authoritative Information'
HTTP_NON_AUTHORITATIVE_INFORMATION = HTTP_203
HTTP_204 = '204 No Content'
HTTP_NO_CONTENT = HTTP_204
HTTP_205 = '205 Reset Content'
HTTP_RESET_CONTENT = HTTP_205
HTTP_206 = '206 Partial Content'
HTTP_PARTIAL_CONTENT = HTTP_206
HTTP_226 = '226 IM Used'
HTTP_IM_USED = HTTP_226

HTTP_300 = '300 Multiple Choices'
HTTP_MULTIPLE_CHOICES = HTTP_300
HTTP_301 = '301 Moved Permanently'
HTTP_MOVED_PERMANENTLY = HTTP_301
HTTP_302 = '302 Found'
HTTP_FOUND = HTTP_302
HTTP_303 = '303 See Other'
HTTP_SEE_OTHER = HTTP_303
HTTP_304 = '304 Not Modified'
HTTP_NOT_MODIFIED = HTTP_304
HTTP_305 = '305 Use Proxy'
HTTP_USE_PROXY = HTTP_305
HTTP_307 = '307 Temporary Redirect'
HTTP_TEMPORARY_REDIRECT = HTTP_307
HTTP_308 = '308 Permanent Redirect'
HTTP_PERMANENT_REDIRECT = HTTP_308

HTTP_400 = '400 Bad Request'
HTTP_BAD_REQUEST = HTTP_400
HTTP_401 = '401 Unauthorized'  # <-- Really means "unauthenticated"
HTTP_UNAUTHORIZED = HTTP_401
HTTP_402 = '402 Payment Required'
HTTP_PAYMENT_REQUIRED = HTTP_402
HTTP_403 = '403 Forbidden'  # <-- Really means "unauthorized"
HTTP_FORBIDDEN = HTTP_403
HTTP_404 = '404 Not Found'
HTTP_NOT_FOUND = HTTP_404
HTTP_405 = '405 Method Not Allowed'
HTTP_METHOD_NOT_ALLOWED = HTTP_405
HTTP_406 = '406 Not Acceptable'
HTTP_NOT_ACCEPTABLE = HTTP_406
HTTP_407 = '407 Proxy Authentication Required'
HTTP_PROXY_AUTHENTICATION_REQUIRED = HTTP_407
HTTP_408 = '408 Request Time-out'
HTTP_REQUEST_TIMEOUT = HTTP_408
HTTP_409 = '409 Conflict'
HTTP_CONFLICT = HTTP_409
HTTP_410 = '410 Gone'
HTTP_GONE = HTTP_410
HTTP_411 = '411 Length Required'
HTTP_LENGTH_REQUIRED = HTTP_411
HTTP_412 = '412 Precondition Failed'
HTTP_PRECONDITION_FAILED = HTTP_412
HTTP_413 = '413 Payload Too Large'
HTTP_REQUEST_ENTITY_TOO_LARGE = HTTP_413
HTTP_414 = '414 URI Too Long'
HTTP_REQUEST_URI_TOO_LONG = HTTP_414
HTTP_415 = '415 Unsupported Media Type'
HTTP_UNSUPPORTED_MEDIA_TYPE = HTTP_415
HTTP_416 = '416 Range Not Satisfiable'
HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = HTTP_416
HTTP_417 = '417 Expectation Failed'
HTTP_EXPECTATION_FAILED = HTTP_417
HTTP_418 = "418 I'm a teapot"
HTTP_IM_A_TEAPOT = HTTP_418
HTTP_422 = '422 Unprocessable Entity'
HTTP_UNPROCESSABLE_ENTITY = HTTP_422
HTTP_426 = '426 Upgrade Required'
HTTP_UPGRADE_REQUIRED = HTTP_426
HTTP_428 = '428 Precondition Required'
HTTP_PRECONDITION_REQUIRED = HTTP_428
HTTP_429 = '429 Too Many Requests'
HTTP_TOO_MANY_REQUESTS = HTTP_429
HTTP_431 = '431 Request Header Fields Too Large'
HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = HTTP_431
HTTP_451 = '451 Unavailable For Legal Reasons'
HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = HTTP_451

HTTP_500 = '500 Internal Server Error'
HTTP_INTERNAL_SERVER_ERROR = HTTP_500
HTTP_501 = '501 Not Implemented'
HTTP_NOT_IMPLEMENTED = HTTP_501
HTTP_502 = '502 Bad Gateway'
HTTP_BAD_GATEWAY = HTTP_502
HTTP_503 = '503 Service Unavailable'
HTTP_SERVICE_UNAVAILABLE = HTTP_503
HTTP_504 = '504 Gateway Time-out'
HTTP_GATEWAY_TIMEOUT = HTTP_504
HTTP_505 = '505 HTTP Version not supported'
HTTP_HTTP_VERSION_NOT_SUPPORTED = HTTP_505
HTTP_511 = '511 Network Authentication Required'
HTTP_NETWORK_AUTHENTICATION_REQUIRED = HTTP_511

# 70X - Inexcusable
HTTP_701 = '701 Meh'
HTTP_702 = '702 Emacs'
HTTP_703 = '703 Explosion'

# 71X - Novelty Implementations
HTTP_710 = '710 PHP'
HTTP_711 = '711 Convenience Store'
HTTP_712 = '712 NoSQL'
HTTP_719 = '719 I am not a teapot'

# 72X - Edge Cases
HTTP_720 = '720 Unpossible'
HTTP_721 = '721 Known Unknowns'
HTTP_722 = '722 Unknown Unknowns'
HTTP_723 = '723 Tricky'
HTTP_724 = '724 This line should be unreachable'
HTTP_725 = '725 It works on my machine'
HTTP_726 = "726 It's a feature, not a bug"
HTTP_727 = '727 32 bits is plenty'

# 74X - Meme Driven
HTTP_740 = '740 Computer says no'
HTTP_741 = '741 Compiling'
HTTP_742 = '742 A kitten dies'
HTTP_743 = '743 I thought I knew regular expressions'
HTTP_744 = '744 Y U NO write integration tests?'
HTTP_745 = ("745 I don't always test my code, but when I do"
            'I do it in production')
HTTP_748 = '748 Confounded by Ponies'
HTTP_749 = '749 Reserved for Chuck Norris'

# 75X - Syntax Errors
HTTP_750 = "750 Didn't bother to compile it"
HTTP_753 = '753 Syntax Error'
HTTP_754 = '754 Too many semi-colons'
HTTP_755 = '755 Not enough semi-colons'
HTTP_759 = '759 Unexpected T_PAAMAYIM_NEKUDOTAYIM'

# 77X - Predictable Problems
HTTP_771 = '771 Cached for too long'
HTTP_772 = '772 Not cached long enough'
HTTP_773 = '773 Not cached at all'
HTTP_774 = '774 Why was this cached?'
HTTP_776 = '776 Error on the Exception'
HTTP_777 = '777 Coincidence'
HTTP_778 = '778 Off By One Error'
HTTP_779 = '779 Off By Too Many To Count Error'

# 78X - Somebody Else's Problem
HTTP_780 = '780 Project owner not responding'
HTTP_781 = '781 Operations'
HTTP_782 = '782 QA'
HTTP_783 = '783 It was a customer request, honestly'
HTTP_784 = '784 Management, obviously'
HTTP_785 = '785 TPS Cover Sheet not attached'
HTTP_786 = '786 Try it now'

# 79X - Internet crashed
HTTP_791 = '791 The Internet shut down due to copyright restrictions'
HTTP_792 = '792 Climate change driven catastrophic weather event'
HTTP_797 = '797 This is the last page of the Internet. Go back'
HTTP_799 = '799 End of the world'

================================================
FILE: bench/benchmark.sh
================================================
function run_ab() {
  python3 run_$1.py &
  sleep 1
  PID=$!
  mkdir -p output/$2
  time ab -c 100 -n 5000 http://127.0.0.1:8000/$2 > output/$2/$1.$2.log
  kill $PID
  wait $PID
}

function run_ab_post() {
  python3 run_$1.py &
  sleep 1
  PID=$!
  mkdir -p output/$2
  time ab -c 100 -n 5000 -p data.txt -T application/x-www-form-urlencoded http://127.0.0.1:8000/$2 > output/$2/$1.$2.log
  kill $PID
  wait $PID
}

run_ab albatross hello
run_ab tornado hello
run_ab aiohttp hello

run_ab_post albatross form
run_ab_post tornado form
run_ab_post aiohttp form


================================================
FILE: bench/client.py
================================================
#!/usr/local/bin/python3.5
import asyncio
from aiohttp import ClientSession
from time import time

url = 'http://localhost:8000/name'

async def hello():
    async with ClientSession() as session:
        print('getting')
        start = time()
        async with session.get(url) as response:
            response = await response.read()
            dur = time() - start
            print('read response, took %.5f' % dur)



loop = asyncio.get_event_loop()

tasks = []
for i in range(10):
    task = asyncio.ensure_future(hello())
    tasks.append(task)
loop.run_until_complete(asyncio.wait(tasks))


================================================
FILE: bench/data.txt
================================================
B48eI7bn1r8m2SK3=vA3mIU9kvWG7CdvT&ott5c0%2B4sAn3vGb6=kvpe1naRw%2FzuXdJn&gkYceLsi5VG6h12l=9RAM1%2BxDzUi3PnN3&dBWFou2sY2fPt%2Fsk=ZIVa5sqwHd239nck&xBFiwiINo4huYi3l=hAHJUzcYkd9kO6IM&USFbLQ6yUf9u6Rlo=608ME1Hrq%2B39RF7X&vuG3NkvM8XtIDvm3=VcJmLqZ0FZyN8E8h&UnGywLaXxMv3mI4j=JAohRjKOxZwJOVHG&cv6Q7EFs1D1CY4yp=9QZ%2B%2FfkcwgnzSzpg&sk8Jp%2B1UWJxxgjrk=ojvsN2OL7i2kX7Ih&4iMDOPr8hvM84rnR=6%2Fy6Pd0441nkzM9o&lcap61jqTQCE8Eam=vjpf0aTd70iuTljJ&%2Boy%2BOQ5I4dSxt1VO=JOJ99iNEt%2F%2BHlVMV&gYrIOTNgvUwH%2FAVb=Q8UsKEbYRj6yHSj6&1uR7QG2M47b31XHW=%2BN2zX1bIWGgT8Ls2&zSbHofgtiRcMLO04=06drpHKlDtTnRUw3&vRaNWSKp8eu3tf6S=uf5ppsuFad7DU6uq&wugqQIByFkAQtBhE=q52Rw%2B4akofMYiJ%2F&TSvLsLlxY93BppAq=FMFSmp6hVnkAyNz3&PFefXBuddYRtlJ2k=8ExKoPsm40psks90&oncK9OmEH5NJTOL1=eV6T9e3Cak6Lrr4V&ra79pVvrMZkdpRAW=JGjG07SWkg5aNVV%2F&g7wAD4xtjnsFCYPO=KJWsjeNtE%2FwIq2a4&wH%2B6fvxyxDGUCZfG=3KEjjKXP3CkCQ7do&g15DC%2FAurLlIiH8l=%2FH7oSssJWG06x8VW&700qdkSFXq68eNTT=Xx2UA%2FU6bi7DSbSF&ZYVb%2FHHJNw5Rm6zr=Z%2FYFCItKta2d6VCc&u%2BrPoL6B0N%2Fj84N2=SwUGQUAeTBCwjThn&nHB4A1QWhf0MMh2Y=r9ExV3mYyum2El7V&l%2BoaA3GHrDiIM%2BqI=yQCAgtpuq4rxfyxp&QYLfs4ZjvxsIgD%2Bo=7bi9tdjH90lwUz3R&Tg4FzH5fMSfwfXos=6yyod%2BV45PmcZ86A&HD9gYGaTbYCoEwtL=95Xk9eyYiumrtG50&mJDFTX8Vc22gAEEO=ZBDBiPqHDoXvTO5v&8XPbrYCls%2FqIGs2N=7Nwi215RsMYF%2Be6u&VRhr7ciVEqL9Dzph=DyYdIaFCrS6v%2Bfd9&JcaR0vbfMDLCBx9M=QxcvSgjnmunbJc1F&MwAB7xQACyHX2UVs=BaiBmvsSAqJPVGZ2&9et21F0nrB4olxg2=L%2B6mDMTjfT7tNYk0&lfrBgHNYzo4xkdvM=gPISFjem%2F33vNZD5&2cXVlKbxRu%2FTJLO%2B=EoEoXreFjfTURzEG&OGDyAdIdhKF2LiW2=bKRuTpDJsSYnRG0V&iFcv5vdM1lsJi4yg=W2DKtDhZTtZWJnSF&8EV25k32IDQUhHEA=e0SqBZYMyPjS70Nu&NZRrBnNxv4xPVuyf=lZmzJ142oV42TAlO&Coi3cwnHNfxBSlUj=V%2Bph2sHKcp75Nj5g&0SbofK4QM2lA%2BZtV=N2KXmYpYghOcJq%2Bw&nQGmQ9GWm2aJPvzX=wHH0CAacHZVr%2B8pg&gXWvHHl0NU0Xg7%2BP=DHccNWDiO9QWsxU7&2%2F%2FxHLbUAP8AdxIU=Z7XSPXPE%2Fz3E5gBm&oqATBfiuCgCPI%2BqP=xKlLvQPavgYOxWp%2F&3iI%2Brzg6Ztsf9pC1=ewXJmnytT4wIjef1&C4hr4p37IjiRDL1L=4FGYYcSuDLXPjSsg&LUZxxiZAGyDgcuyX=hlsojs1WxSb%2BQEE2&VBIf5luVCH0Js08s=IPBlvNgFcUtIBgM2&xwmqKqjH7%2Be61laH=we3OB3pUR1J5qV%2FY&sy%2FH6Kc5%2B%2FxXhUsP=taF2Nl3NIJ6ofiTe&oZLLb7PR3XikiJI6=rVMCzOFtOxBPjwd6&q60Mr1nYtUOxIoGS=Wh8dtgLHC3SKqWFG&URWfglNjEyP3d60N=euWAwuKu5p%2FL4qTw&SaJz0F3xECXB2cR3=FBk6%2FXii4puHqCXi&1RzFeqBv3W%2BmXtsR=0zmXbZOZ3ntjHmFA&%2FgOXe%2FKpEm2kmOPo=4ngp%2Bh%2BN8pJeQF4H&Xae0DsLuF0QnsF6v=7ZWTDH1FnV2RWD2A&tux8myCiGZciU6MR=GRLhYRDzDm1oHQX2&x3xdb%2BmNPFNg7qPy=xdWke70J3ntxDX%2B%2B&mYS2caKTyt8sunqC=K2q4To%2BwafGoQXwL&zbRwLFQc0MXuAqyS=y6S9UGKqoWu43lYC&zJ9BjtjlU0emuSiu=SLV5x6ohpC0xHa2Q&gbgzS9Jhyh2vrzsy=d1EqARZhvEL9T9B2&%2B0acK69I1Y8%2ByV5W=B6xrwETccO1cJQvr&fc5Fz%2BpYPhvDZIkY=CNSZzp2p7XJl5FFm&MgsODsEleooK0jG9=ibYBo00UjuaOj0rO&QA0%2FM79%2FhXeCwq%2BX=v8aaZSrWyvynUlgT&pciQENnGLEHvjBOT=l2195XYilBi4wHbC&izLuy2eEvad2pqoR=uIoZvDVYhsmIYgm%2F&KvMJDW85LmMSLmrd=y4AiUxkHGxlHG2%2F%2B&yMOqnwRj02uLysB7=7fCHhad2TlQiPvZt&9IY1rw19BzwP8jD%2B=pE522xz%2FvwTvWqW6&up5hruxLgUtJEui2=kHbZegbPsrDAAXwy&kw%2FE9ESN%2FAyjE0SJ=iIdHNi2uBc6XNrs9&Wrucx0vzeSHDClAi=6SZOJ6qx0CsVLuOF&XNRS8t9MCCgI5gWl=Dp7jphSpGvtiq0DB&g34EJKojKGFK8gKU=%2B7c%2BZs%2BqbIauNMnD&h3NYwBTdiABRKDUa=KmSNnuB5th2Jkmp4&3QtYHJxCSOkT2fdL=pBiIMOTojZIK80LS&JvcrNxavyheLp8Pd=MzDCjNVGlyRR66DR&l9bnAR5tQLAIwKac=oP02kTKcwRAP5LL4&Av0Z37JjxhVvnONQ=vFA%2BN%2BJlQGHo7IWJ&BcWeCKzFaRPSWliw=tIuyJuUdXLsVx43l&kQn0U1WfPXgPiUjG=zNLCKo7VnG2FGgYA&QbMGOR3vv%2FtuIwqN=hBF3t2wGYhcfDRsk&bxjLuH%2FDCmiBboeN=vTCnBUE7x0colGYV&mpaMxrNwKlBc8iEs=hP7e7tPPL%2FHmt%2Fgw&6ShOzeXHkk%2B58MBt=bHpdZ%2B0EaQzElFwR&ZVoX70UiZ4I8yGk%2B=0rKSBH1Q0xNJCrk9&C%2FvxQUM0LJnOxi%2B%2F=FMod7Ij2VpikhEEb&F3qN%2FitwrA7EsIqD=5dG12I5hJT%2BGM5Dn&ipB4r6jC9pNa473d=QyN4eer7tyCy%2Baez&H17lYS3qBROF7pkY=4SkF%2FlvzJZhZ%2FG%2BF&zrdKzxVSwS26pNIi=hxETUhR8Z97fLHA8&UXXTXG9NIFM1Nb9J=iaV9Qm%2FUMjf6VSL2&qQOLwbK6eDsSLeLJ=RSc1iWYfsEVFdhMG&V0Ca3bcN5ui8fzIb=W%2BtirDKVmT5orWrU&dIEaT%2F%2BbBWz9vfSm=32ItVPfIQIFwo%2FqJ&l9B3A803AqDEOaJA=mqxWNBvoOvSBWTgZ&d6qZAEgy7J5phRsR=dCxeZStuOBpRgTsW&8hpl4agPZZ2xHPzb=TMdqKZrOt2gQ0ce5&yL%2F6dk4I8bDw6EMy=AbnkDdInH%2Bzh%2FIrm&WHgSyIWfnJrkM10O=S6Y4yIdDFM1pB7ts&eN6p3SQGbzlx%2BU3s=o%2BwvXel1lEFWnCet&C%2Fw%2Ftl%2FHCiGixlST=mQmTOBWFFamt2Mfi&LLaCwu4pQzzmX1yH=OdVH10wNVpISdt8C&pet97y6Z4JfejkTV=CkxvPnrgNbHiHfir&iO1Q4Dkkm3RkKYWH=u%2Bvryhn7Qp5uiwmm&Tn4BOVa440jPpEP3=g3lbnfgEismOCJCi&8SHiGCEGcziQmPv7=DOIkvmHmOR6HsCXV&Hli1gzZ9yw6mTRHb=75cQuTqbh0ZHgdoZ&%2BJUtOTph%2Fy0iUuoV=ZF9ulFWInRqyU8MN&RKPO7C3h2hGnMrZA=QN9pALjCqLZb6dfN&4mrfczWt87ZhQlBi=oUbnhgjcpSoZDq8j&ToK2JUxHeZ8eH8Ke=sqIFBZdAsJnDQDWv&xrPj%2BohEkCCcwFg9=cSLJRPpSTfSdwZJU&zZxXkeUXjE7f4BDQ=pRIMUdOKqV04ZA8K&vz5pCj1fLpGss8M4=Xz6Vb5rNH5cHVuM3&cpzXvJanzbtx5%2BaT=4Ry9iVnrd5D3JU%2Fj&2ojlVyCUOZKj%2Bjxf=%2FUYNIfVkHB3lR4si&CbLKAAV%2B2MAr730N=iXAskPZhuOFfhwEp&A9PyJlAzWI%2F4spPI=ltT99l1LQHvWPDKq&Wdpj8dAU9fE%2BzWmh=FYV7u%2BXAO0KnEfKe&%2FQX3ToVP2%2FcvBIha=jfTu7lt1UKmk3GKS&BPhQRs%2B%2Fw0yQDvgw=KWOZLgDjilAVYlzR&SEbWzLFsIFO9BS2T=7AaNWjs5rAUESQGX&iGzeRqr1VO6fuZSZ=j5Xf%2Fy9kXmS3h1TR&b7OoO%2BfaNdHnE0v4=b%2BLZWXVj8xvkplol&PbUdljzBACJfjzkV=7AcFBPbxh9NnIXBn&vyfHZ%2BGa3i%2FUllXi=gQ5UglL2Y%2FPSFcdg&BcmaeEwIYuoSBZla=R8GBYqolW3hB%2B1TZ&1ZUCPjXvOyg%2BnYrl=Q6063KAcocc1h71J&AfdOKGOwMHaADFCW=jBBAex9y73VpvgDt&08G80L0g%2BtkMmPiG=YY148Ynz%2FQ6F4OsO&2AoVFdx8nZghSE2j=J2kTL1mT8FzkCbKz&uhuuPbYrtRQWCy%2F9=NisKZSWipamjqeNj&I8EZSJiBzg1EGRsl=vRvVpvJsa%2BKUTZQJ&8hFIb7l83bWN1S%2Fg=%2BAKD%2FosCx2jjtDYH&jN7c3eVlBsTZpbey=AboVl7JIxBD%2BFysZ&Yksk6XRecFSjJIYN=zOEyqCypfvVkQtF%2F&u3AgU0ksNya0ACSE=qE6W0F%2FCzELKneKZ&8ZMjrh0CSpRrDcUO=uDq2nkN6zEj17oof&Ul4Enn8BMYfTpI7m=eA3di3pK7vMidtXr&5x7ztG3Ex4IRtqGc=PMYkbwK4LdD1sI7N&VFZ%2BvwCDLyzjl0Ay=8E%2BB7JHnxuvfYwU2&370EwctECba9ko0i=gFImZVH9auZcCkl6&CbhWg5KLhEGZz0x9=HUIbmwuh4kVp%2B9OT&24hX2abTMmi3F%2BTe=HG6aTylw2oXZxWva&J8VYp8hg9UVuce%2B4=wljocdBEXVIjp1TV&5pXRbTOfK4AgrrHZ=DTL8OAMxRyEHiFcm&cUNFk9kjtApCggGu=DVyjdKwBn93O6HVb&9XIwwj22xe8Hzazd=mkWC8lsVnHBkEfPx&DyrhgzYsgawuoElK=dk82UZll8UkEqua4&vGyZhUWs%2BzljYTI8=uwNE295rwUBrR08x&vI8k5lgPWcA%2BmbwE=whg0GVvNyiDGOQ6v&Kj9AKnHQMMbKX6k8=YeW867a8TNa9vEml&501%2Bjr1WcH7%2ByMz%2F=dZMXtAcr5DBG2M22&6Gd9M%2FL4LEe7ozen=XKzbm0ncrVrdi567&LxZ5khnxkvcFMLD0=fRMHaMuqmpS5Bhli&TPOsYbHgep9eykag=dAkunpi%2Bc2%2BuT41K&sJtEvpeV04uo4LRX=pxaXKFsa%2BgMbcNAp&j844udtMzy0nRytA=MfgSEKQ%2FXPsGumNw&hUmg7HtKtKgn6v1A=YA5geX0amoIKWfEu&JeUK1nqPz%2F5bjdr8=i87KlsdRZiD4mV0T&AZgsPaMlSiwIBm9n=QhzunJ02HVxwR%2FT6&tdrv3htz6sxjrUrK=us4txrSiCKOtbW3V&XdnDLoxTj47ZowdN=SRvYUhHKH5IgFYv2&QlSIiGFU6ur6hLZC=vVUwGhbjV9l2Po8n&v06BXP9UjMlnptHp=PaNBrZkybUvZxZzG&14wQ0vF1PSjASoXp=C6D5ToaZ43BPp32W&h5m4Q7dHEdo%2FfviE=0XE4hbhd4MbCju28&NxYe62JCUMKmE%2B4b=n0FLIXovalYjwkvf&lhTtNFPmPAI02IXF=BkofpXU%2F06mX9DDn&KRANst04hd83Kw0S=CWIonSR2LFSoiVqM&ZuAA3aO6UhCetGMU=dF4ndF4kY6Sb160%2B&ym%2Boy4mTd%2BkPkwsB=bVjrVrcyRvb2K8bd&m11pnWJCf8s%2BvHBC=RzS87GObTkLXMelg&2Pec0B2Jhw7BQdmd=pyM3ILgTe8NZfNmr&JvklmA9KDgCv2Uka=kltfO8G76tveKQjd&tuJVIY66QNZxE2ec=l6b83VcRxddUHauH&DQI6MyohAAHXo0sU=5XTIyzWApMoD%2FFLX&7pTydPzbdWJmqDkY=DlXVic%2FbShjfOmNW&YP3yGaTQUzBlBDhC=BKM4nXCgGeLw05Fg&%2F0vLN9OCK4RrLEx%2F=dHGCSWFZq%2BhzyB%2FR&l2KqH7CfotwowL6V=HQDNN4JQgiHS5bTr&aS8WL%2BG8JhySW7xs=ePSu%2BSbpOkXjgpz0&r4tRZ0mCFJlsXLFz=PNggWZ5UZSuPfofD&FSdILG5indCTTBkO=GuY27TghB%2Bm88wTu&n%2Bsih5bmgfZrDDqZ=5DHQWs%2BViVkFb%2BtM&9rci%2BN1nawfVjOWM=gVLGbo%2F5ZMvCFhxL&LS7QSQLs4j6bLdGY=7wC9aYf0yFjLGgRT&8JCVwzUmJDJwe7Lo=1SBdmUNQzB27TIJs&huBgKj2dT%2BGiZzFs=cTqPmnj1W%2FKicUh5&nOIgNZTjhozn8p5y=lalCsCepOvWr3BxD&9u87Vweue1ue%2FXlp=qIfxL0HCX3W1obRg&FilUpB9b%2FejJu3a7=ABowE5INf4YyKQuk&IyA0s25Vt8j6dy3P=s0s89SPT0BtJMBfH&fW1T8%2BHkzVSSUSVZ=4uJjasgj9phC%2FOu9&yhRhIWk4yyVdlZb5=MLyo5iSqnbxvuTJp&gItivjRRUDcE34Um=TAdcD9vGcaVUYt0A&I2K2a9kjqmh8A1gZ=ZB7%2Fm3At3RVf66DW&Buwd%2BLJLu4KkO3JT=60A1Kkg%2FHvtsQ2WF&hle4aYn2OTTtttfS=Nd4PdKQ8woWAbQtj&Y7MVIba4fVujU5a9=4u9KjgMTP94D6n06&aNo6iAbafT6niB2b=PiJ3uupZjUvb5fMd&Lu0AV%2B3i3YPM9pPD=bGSA9CXJzGJYTer6&2u78Qk03ZW6ozbby=uSGnMlCLg29V%2Fa2V&Vd5rIIwZH2uuJHf%2B=yf2W3E5FPXpMLhxp&P0IySqCZ3IsH%2FWj3=zXyU7nG0Nwr4Zw19&S57Zbb6f1RRuAsJh=ehPAMon8dawllLod&UDvkSmG35lZrZc3V=K56SGUm7A%2BMT2Ndg&VgNbt9PXNt8b1kBm=jnsrBKMeghNvhapJ&CoUG4KOHm72UjjhF=FggHZTCR%2FtMZxRnO&mgMzWKeH3jD6CWSq=wbmQElfVPt%2BFO2ct&RLz6jkgUZ72vLXRm=Wj9PCVGjUeYi3%2FeO&brKx7pwTzLF36C%2Fx=h7vzMLx2FEhZe0VW&WuA%2BIJP4VOjZFVU0=1cnX7Kd7Dc%2BKaD9l&nKfUNaZkaHnJcl6d=Ko9TPRy%2BHtiu%2B8%2BS&3aT6uyy3xF%2BynIH3=XsJasnrjpPXKMKRC&h6NOaK7HeGBNQYde=HajdxfF5ZljgaIJr&dIZmGyyRTSxXIzSQ=Son9jek0KlECUI%2Ba&v0%2BnuRqQWSoHE6sv=mV7MDi%2BOiyxsIhyL&e9wKktz8SQvaFQnF=y%2Bx8CZeM%2Fy5zgLWt&MyNEI8LLNWa%2BKGM6=tGsOX9Unlg9uMo46&XjVZGuKbXlytpkqY=Nm%2BtDGkzP%2BXrjZTx&84ioX8DU0Z7qcAzR=qAd4aqjKqucxHj%2FP&yiK8Tmnp7GediZTn=1RzRIzIG%2BRv25JD8&QW1Pb6WRXBF43Ax1=ledj5s2eJfbSf8Zs&Z%2Fm6XifHvyLOh2k2=gAjaumlIkq7Z3K4x&WsqUUs7o2ZOvf4rX=fcMxKsbBMFjOqu60&x%2Fm8KirPzTABIOzG=hQsYvDncZqGWn4UK&cGQn3FOGfQAzGx41=A8V9nK1f32fAkO2P&tLS0C%2BIGn%2FFy2KWU=%2BZ8vA7o%2FXQKo2Fl6&czbVuJxv5avg1nwf=7mD6OZ9Vgb9azCsX&Xgpwr%2FCfNacCLCC7=Jo88s2bM%2FHZFWTsM&PjtBirifHgUjGjI1=guxwpAgrWlziTgjb&hpfhLB6JevL18BSb=vcS6fsaDUV3RgWCm&WGixUXWfJch22hBH=T92kfy%2FOoZ6LO%2FBD&64NR0yD2xWiJmZW4=cV%2B4eXpBGGnxuM4R&WsOYvQbUVzquVCbI=NWtRqrREVBar9V1N&tewli%2BX1y%2F6ZSGIe=A0XvSnrNr5dMAc0G&9C5Kar7a2IU4nxRp=91Fb4Tjgx1R6QAmk&Cx55TKuTzPudOnE%2B=JFKqbZPX5hdClcPQ&yN9JuN%2FSJK6Wttqi=Q2AfRY7c3oDQD1Nq&kqczCquMuHMbbh1K=%2FiLD%2BigGyX52hEQi&tWuoEsnYJ5xHx5ac=3ToAczpui7MIrK%2Fn&sAqHRrqs7ncNLTmF=HtkBdtnTrqCQIhBL&TMOWLZuU0AvsVGyE=zDrcqLjd2f%2F%2B25%2F7&fW3TveQcVPd1GKm5=xngoUkgzdBPTAAx%2B&nWlbuCO%2F5rKYtUKF=8d5TnqLs2ng2iNPn&puBbjD3zSE0M0Smv=NXsQw%2B84ij19mb4O&alOOc4N2YhIMbUjQ=lrQ1KupgAvFlQ0fV&SHJNZotwulYyxGeo=5ZQaWBUJ43ob%2BNbY&kTIql%2BcKXvKbQjIg=XTCplovjv5bUDuH5&GLSWPz0VK0u3u4iw=1R7K%2B%2BYrD2dlIt5z&5uVZJi1LVlz8yM6e=tzgJu3xLI9P3gHNM&UATQY9HQDZUonqI%2F=myz%2FQgY2szNxyr%2B7&sakNosU%2Blmk60BqQ=zuv4UkpM5SyzAJiT&cNjo2KkP8OPYrYpt=ZmjHCoNnyCJnNQn3&NLfcA4tCid7H0Nvz=owX8n385LzmiTMX5&JaiUVtjcli6QX5uL=pwH9j0zkrFs7GqYz&2Lu4DumDhmM9gjgJ=VboF%2FO0ZR0XKhZKK&Jv2KUZ%2B6t%2F1XuCKp=aJ%2BX0B96HB1Wg2ik&YbsSKcHgJtcFxtBi=L0AWepLq3%2Bj0I20Z&Sc%2BcEfD9Uf1Ax530=JCoNJVNzZJJ0Qd37&xhvQeUwAmMPiMCeB=ua0Jp5GISHqU95kO&naFncy61mEuaguXT=LoD3dXI%2BKHeuQAjE&jiLok%2FrT5PvNMLFK=yaaje%2BKWCKzA%2FsNG&pYCMXxeX6VYn7TIS=7zQsRB0WfQKLrEx1&I0ObH9tp9G34CKWI=OQsTSp6%2BILMtnS8i&MumbgpMx0CW5g5Ou=8y72chwUmAV0y0UZ&ibUYXkLG5ELfsCw4=tg1ymIpRcVdlazIC&yalWtKhhNgTA2kvU=dvu33w7ddPfhTUiz&2NtDPIYlcVA2fBQi=4oj9HW4QAX4Cw04R&nKi5hhk7lXN9umcF=kaDd0iNE%2F9Y0BlCd&Lw9e44NwTObmO2BY=hl53JOX%2BojoMqJUb&lYXn%2FqNOv497ePW%2B=cICOOffay7InAI%2Fy&1G3PLym%2F1UUM2w27=%2FJ8Nms5mRkKZTXtS&UWj%2BHfejrydHavG4=wGKBgP0KZXiJgtbT&arlsoW%2BSLI1FLjZs=MFh9cH%2BqRS0EzCHy&OAZtQTBv%2F7b2lCfU=W%2FkNSUZ8o%2FbKIMHr&0z7EvYDXBzSdr%2BXc=fa9WgLdMdyfivRXt&Rlxr7xGepZ0eDBiB=tmASMq1tzvdW3hbQ&TvxJVjSqEQMViwDG=WhJOoDzYsDKRww5x&EQwztHJrsO58yiEb=4LPRTK4XtkfYR%2BMz&5sExGJoTLEJHwE%2B7=XHDbQKGKqzeDK4H%2B&sXZQ5SiFGat%2Bsq2j=CFl0rVwM6uOPZjVx&QrPgzBClw5LYJsZr=f%2FhNniHmngo6lBr8&qf%2FC2%2Fl6Vr01Fla3=56kD4tjFx3GhilFe&sDgKATF2HLUtooN8=udLTqcv4Z%2BWvpJyb&bcZKITE%2BS2w9uVFb=t4ZZOJltN3%2BUxbKb&t59FrZ%2BHFK8FgFzC=hNjYRCc23TwQuDPR&9o2nGFgFVYA2jGth=NIK2OLiHCE23APyQ&VZONazichvW2B5nD=a9zMcpYfKiFoWi1S&fRdbYEtvDgI%2BwUGU=GFPPPUoUwFuznsO7&lnqskG%2BdK2eSl%2B2r=ykvN9vYcaazV1qW8&WjyDt3DOGQN3vbl5=eOCBxNcnQHCk1ERD&uHoE5zmfEpIMlSvX=dTa35GmGORAEoGx8&RvsDdUIjyjmEd64X=TKZe7n%2BenM8VE3sA&Q19GYINFaiwj9yTA=Fcz%2FL1rD1N%2FsSGpn&c3kDC3Zfd2Jk3h%2BY=Bv%2FxrEeCoCinREBZ&dCjdYH8ENRHcEKj%2F=om7vYOZ%2BMI%2Be5x2%2F&7sIhj8bofnq887vu=uSXYTMLk1%2BFVCPNu&I8vbj%2BK1QsGIdIJD=sNoB1VzDN%2B8mTfW2&rDDdMDGn2FBAK4Yy=8PqElh0LOKWtuJwl&q1aNc4x5io2tVjDY=U%2FPIs6cMvHEr%2BeO8&50RmdN6C0R%2BFJwnN=vvid5tjaRam%2BxNho&oED1vvAD7OaGx3MX=fT2wpecOTiu%2F%2FryG&V0pDF2eTuzaYU2tP=G0sfV1JlOkhO%2FIst&NLNsl9d2V5wjq8P%2F=g8JwAAepMlAeIHqA&%2FAqq8yNYeBH3n4Pn=4uvyJQGDQVW8UvUn&S8Rded2sLMDPgNZJ=5pPSgp%2FggmveQW5p&5P7%2B2iEfcjLrCRKo=TGQ0%2FyCl3nHCsN%2Fp&iiWscdVzpMTFcKyM=%2FceYyGsaOeWsj2%2FF&D%2FpL3HHsv6BXQ%2BcF=ahIW6t%2FnfwYcGSB2&SlPAYs010nyWWMZO=GQ0%2BttXwNlmCX0PY&dXC4z2Fd9K%2FnKv6C=4p8sz1zvWXBWKGa7&LC8bF%2FzVDt2TRVQu=eUM4Wg0kAZjZ2r6E&d4sNfA0Uq%2Fn3BzZ6=GwvR1%2FEYnTcdbQQd&OlClZ7Wk%2BEtmk2eJ=s8W7riMgE16y3teD&ZhrYLvSln5TNYTFE=JI8s52aMAbzPrss7&eqJXM3Hu6yNAbpLW=fRU98GmZl40kf0sw&3Dj%2BpLcgi7gryyPc=1kedj8h%2BR958Q%2BGi&PkR2hlhnZi6vpF10=d7fV87vgue1Y9dRv&y126tkfhDDxIdg5u=Dwm%2FfB2tNiDrCLD9&faSt5Wu8L6fPnZ78=lZdHUaFRBuReujcp&hfvK3LeS0mYeNHf4=ige9fEO%2F6CNBv3DU&yCq0yk7TfnoS%2BaQX=JreiXHfzdtj7WxW5&rfA9FQA2tIx1gh6j=hCelGoSzSBeU1i71&usPCWgwSaoECAePJ=PhEBunhFGi3wj1rC&JMu0lG79eS09nhoS=V5JvaG5vzYXd%2Bzvm&JrLt7tgqnZRGvaa1=FfbRfCaNpQcvKhIF&%2Bt4Mje88Wbo6Zhjq=wMxIlV78MMYhP9%2F4&2%2FmvWmkBNyATXt6B=xN4mbGktNM52oZDN&Vn9PgP%2BKwAyubI9A=oB5iwwo0k%2Bim8Y1F&RmCQq0WeB%2FoX%2Bpuv=J3YSvsb3Z87VeJMf&WqjC52lPPu5UqoEP=SrSpxyB5RkajKQxu&8xpvzPtW23ZAVzQu=CqysV8nuNUK6sKSS&T%2BwMl08jUpIU5I%2BQ=5%2BUqICbyZPIMOF6a&vKMpVuHQ8rKR%2BVmU=Fnz8gHAA9b5AohUV&W%2FYXmvFDrTf6wu76=po7G%2FtWYW2bfqh67&NhxCIgargUkHLg2w=yA9r2mvLbeeD0FIq&8aqt8TYh5Q04P1k7=su2xDs82eNmFe0gQ&ZOYOKvUWBuXKSKSJ=8H168w%2F6udDZJQoN&uIJ471LqdXvx71vB=HU1SxiBPjz3qCByJ&hv9C11ZR1QLC%2BYfm=%2FUgRBbNtvNvbBwr5&%2FtR%2Foyti%2FYybH4uV=9qMZY0pYqvmfxCAo&4kLcfH95vF%2Be%2Bj4W=ZJFU32aUtcadpmnS&xiiVS5bs0%2FrzykcN=A8zZ1YbjVdaOlAzK&bP2jYpvwZKAJndBk=u6ky6HBW78mm0sU2&lrVs8qOeQlNX2kTz=eRAIMmXIU%2Bc%2BOpA6&loFsh8vTdDFVgZat=fxAwP%2BslCKSz%2Fh2%2B&VKQf8std27uaSzzb=KDTVBF7cSaprhV0y&HEbIIXF8vdghaj%2Fd=1Cb01LJ24tgCvkJ7&JOzFR7J5XOlhXTRY=xR%2BmlifpFKSL30t9&kehB73nDqOXaw8GR=FYqst7wfA2hxbCPH&bwujCnUabiBGix%2Bw=dKwsV%2F2yaIj6xZ3c&gaNrtardVmf%2BhsYc=9mJ2x4yR9INelD%2F8&VW51p0RqxJnpw%2BKe=IUXLZGqindCZf4mS&dqUk8hGSoKcvLFmQ=YmGkqGGzdL2U3e7S&frUiGma3ItMgkRNv=%2Fg%2FcreYtbJ5XkpyT&UICAC%2BP%2B9GCTQmJt=Mjyusz%2B0lIZbH0rW&zY7JaJQZiZ%2FrKCCo=V3gw5shgXL2eiFJi&PJsp7BKDTxpwUDxA=P2qVvzoQHtIHN%2B42&eA6c4Y4X9bEf88aK=C12u%2FfnYpBcqt84b&jownRo2jGrjLH2%2F4=tvqwoGunEdnYyV08&%2FaSkCPUK2RGHqm7%2B=1SNurLDgtrxqR5m9&%2BLG4fJqeXtR7cMWF=TiX%2BUUvmOSTzq4Kr&T7JbJ2phbupC2Jmz=90%2FpeUHq9KLrZqyj&%2BsH2Y9FA%2F19gx2Ue=%2BzwzfaAolmSWRphu&5gKXl%2BbtGuRhANsh=Z%2BUJVasmBr3tHw9m&U4cH9reL0tRKJoot=iXE4wRurMrHfczeX&%2FgaBW8qaqYIF8vqq=kljr4lAlhO9AmAHy&UTLS5rUuQmwA0qXv=Tx0A4jtoxzA7fE%2Bu&RQs586v71MwRb8BC=EoAsyEZ5hxH8jq2q&TTOreaLtrasqdUop=iWSa%2BLiliRd0htZ8&83znrkWefokuWaOY=E5CqwDr%2BTRa4Cp99&M5soABGOITDGmmbJ=ranG8A%2BPHX7LIgJB&sUTpLjG8qifMv04i=f%2BtDrgc93OBu6KC8&E4QzQo5AlGhVsnon=mnYngPq3SBdVcVE3&q6CSs2bOWcINTAih=S11zAiqyxrd5W1Xb&JKKe2LdVy%2FO2v%2B4B=OJr9tcCZGsg0Q6tq&EtrS3dWdOAxpPn%2Bt=26s%2B6jqpv1BAZbJZ&%2FPOASwcuk0myB99v=9pRb%2FO%2BrCrKahetA&%2BT2P8MoqPsyNoIWb=djEB6n08LIyPP5nh&ccdtjMYTesETC6T8=mI%2FuGgbdAO%2FBARx6&Xif%2BQjrE4W7HzU2r=7qFo4YjCh26ONfYD&c7dJFgUL3NBhcio9=gz6I7gkdNNcd%2FDr3&XTkBUeyajVF%2B7Z7G=XVW8%2FeaeTBomLiOp&rewTdxfsI%2BMN%2FzBL=UJBo%2FPU83BQcTktz&2Ejj7Ej%2FlHM3RVHz=AIJiFWAlrQlNky48&DXCSJPiedJey03s2=kAHVlkbuznq6aAPQ&K65BJUl%2FM4styMIx=YpAoIUOVU0q7QE7Q&8hDP2t0fMEICKKNc=hRrzoOrFm8BztKau&J5QlYoyiSdwcRX4H=28CUhBfLrACpRP8w&uC%2BZdKSmNsrKjhzh=nIF6pl3I5Dt3y2g%2F&cwIkd63JZAjhg0mY=SdOxuqEtRnmx8oP6&kdio1IxKD00UndsN=KTLKrPzqT0001zXe&o7wQtAguvt9j0osC=JBz2HX1%2F2D6dGbbo&3U2at7vjB%2FCs%2BlLh=ozOAHBYu54LqAvsZ&B6FAFb%2BhHdLVBb1d=bwaui%2B1OCEZgEOI4&ZzMOGapguuiq5MLE=C0v3mw8bVQ%2BO9Uf0&IAJ6P%2F2k6Bl9LnEy=dRBODo3ApKBh599Q&c2G0DkqYM8JyJewI=Z4v9O%2BK1W6z52X6E&zDD8vPqJI50g4Yrl=fUbMDFSk6XQ0%2FmQO&01jhZT2ZOBfNkwFP=8zyBj%2F3COCcaDIBQ&%2FgIiDvtYMvI7teGL=dokMdYKeQ%2BbliBqR&GOuZpALqff2%2FLCe%2B=sE1nhE2wnH5nmJE6&zhGYHxx2VZ3AzbQS=%2FiZIjsOK%2Bo5OdeMf&eIBQY%2BKpsKg4Yg8f=DbcxZBQ0APv2UMdy&nzyMYzS%2Bufj%2Bl8nM=fs1PQb%2BhVd0vpNer&wN7afEyBWd%2BKrHgW=pEHDgiPtLC7cclcZ&2DhFmO%2B2ydRckizi=i7L5cjeTFJElFQg9&WH%2FKTnEe2Ox%2BwsHR=T5mGutYn3yGSARTK&UKwz7jSj2qMOQ6Rg=nF5x5lZLOMpXE%2Bvj&qlj6aoj6w5rWabwr=7vxegbx3VAieHIgx&dh%2FsrxLUmr9uzhEy=HGGKcTkOgu6uF2jr&ksZ%2B0BtiHWq3UN5A=pmkNcB80awB2Gdwn&%2BJl8uH%2BVTvPzlNwx=tQXux2jEv0LvR1g3&3fk7WfijK%2ByI4b3V=p75mg6ME6FU6DnzT&tmUpG048kSnF6CkT=MvvkirQgo9j4ezfV&jLTrVCW1Ke4Ykd%2Fh=KSRLtMqEar9mrVVt&1Au%2BT%2BswcW9Y2a5v=MA2%2Fty8ouHnVvoQp&A%2FFs7E3HCqeHw2yk=Lx4hAZ7Z7FBKWmod&ErNOniprzyRNE9SV=FY4kA2taTU2HP%2BuN&WUHGXSU76ClGWRmy=45Tw9TX1K%2BvyDc24&Y%2B%2BsmaNDF7g5EC%2BG=UPbLjNNSsD1TMpkf&jX11QVDtVsLwpIne=bFwsO9UMR2Du0z5F&2gJTIx%2BVyKiA1Jb%2F=R%2BvIqdpedC9xViDP&X6hV70vXDX8t%2FDrA=HslO6HGVODR5oQb%2B&15A5riMk6ra8BEXD=OQiWqOndieC0fLuS&6VW%2FvMZUhbMZIkkJ=D0nVXlPQd7Bq05ge&hnJcaseALrCtm%2FEW=k73I81XNxvPU0e47&UOLzzgyc9GBQ5Ln5=JSVH8GcysbPlh7I2&uE9sezYlo4YvuZGH=fkMxKmXpUlw52dzy&7g6XM5wqLciyAwrA=vA4kqD%2FxQ0FxLEkC&F92AealEhPE6cE%2Fw=DzYFqNXWjdh8lirW&btW7BHV8VHoM9Zy5=8OBo1aOM%2FSzHY0bB&sWyIWX6FhN0KflIb=yzErU%2BWKZ%2FkjtVmz&0rCUqDNfRNuyYOiT=gCp5SLH63xqclP9E&VCZrOJ387Za9edqI=ooBsZFCzH7VetqTj&bHQkkj4Bg%2BZftRSP=8NGtC%2BLOB2CJURJ9&FLb9pXHDa%2B6sluiQ=zAnFp61J2UhL6Dsc&6J9Et3ZsVaNQYDUT=fWYQcVi9RHtliFkv&ajo7NipJSrMheNS4=CJXLFFQDgQiigq40&x8Jlg%2FVWRMxkKzcB=NLDPrz1HZ1tBhmw%2B&A59oTf2MBn9J8pLo=gx4lqzyuBl47gDM4&Wt4H%2Bp1XSxM91zHk=%2FXfcZcRkik0V9W%2B9&F%2F%2FBI9ukUPkNjKf5=FdhAY%2BTwSUjwO7dC&TF%2F%2BXq5HClOUtujd=kG%2BBzP4J%2FaB6vVIz&Sr4NJt7cloaCAbs4=dPEg4B5jML0w7%2BvG&RPxxEhjNsLQO%2BCz0=9vcfjmMX5v8vFY7X&f%2BCFBU%2BxH%2FkHfwbX=hSMih%2BKKGuvFnlHy&TXrthXT3TU2I0Ebm=dV6x0HaJTtwoWc3n&TlVjwnrpNldqqo8G=SE%2FAOtU5nobm2GKy&NMaZwLLwLacf9I6q=MYlYqksE1JGYFWJA&bauQS5Rvm02c2AZw=XfWriFSmYwi%2FLBes&mJ1e3k7xTxxdcm9k=9H6yozE5xHi1QgKX&V%2FyDudA3PfiucZxa=i5r2CfI7iKiEUkK0&LVFFqlsnJNAiaBQv=cA8iaPFRyQrZuS5c&31T8x9Wxu1oyqNM1=%2BX35i5gGcG3XWOLy&gfy6DfEk1mYlb0en=WcTKcN2qFvmBTUyP&qVwu%2FvuE%2F8PSEnq4=2%2BbyhDM1kB1mVqtD&BOPaVsoCJZSWvBJS=p%2FjK6VcAjdSvkx1V&WSDffEL12vDyhITe=TK%2FdmAvGvulFar1h&qklQbqN1Tv1R%2BE8M=nUhwLMdZqz4ihO4D&IrgKTv9sI5e91cUC=ORcubE95r2097aet&OYAvyGfOnuCjVuac=j8YsZWblbLV8yyPv&IS1o9p1SMGUgWTqJ=%2FxL1z5WWDLN%2F09me&P9IdHReZqgQ%2BX23p=f30Rw2vmdpBPOZHL&TDxqDLbG5uyDgak8=hd07ol1%2BKxKAqy4r&OocFYudNPfkK2IKi=wxxN4EZFFqxEgGtS&oRDCcbG1TsO9ATJH=XIPq78iPC0XmlOYj&m%2BubFJFbiuTNVa24=rT%2F69gYBlrsnPVWK&Ivfxr5rMfAlkcMJR=tUpfJzN%2BdU2sO1bi&Sx9%2Bz57VhbwRLZxr=yg%2FStUBADbr1dbhc&EBa3w%2BwKY4CcREHs=FjYzFHO1v9paVLdw&XaNM1DBo%2B3E%2B1KLF=dU29Mhm%2B3eCbUVyw&6tqoFf2rMscN6oEf=0TjfjqpYWW%2FmjJop&83UiWB8ceXezVXUP=lukK2XU4758GYPL2&blt7TwbCHB1qRyb6=do%2BTJpW3pxWFGzp3&XlZRvSUmA4ycp%2B%2Bf=%2FWgJebFafblSDN1b&43xL2qbt%2BQjNQ3%2Fl=hVKpxKCVmTf%2Bxw7L&omT3HkzricNuehxh=khceRjvt%2FMLNhM5T&%2BgtYa6LGAnteD%2BPu=QrFSGUTKLf%2BFzDiR&%2F1EGQW25W6N0I4kA=TgB%2B0951VVRgV0Gn&0k0Bun%2F2pSgbtoL%2F=OV8ASzkCKp23jbJM&m1wc6KbzymrXf63%2B=Hm4yfqzAyMOvExaU&kJdw%2FfAUPv%2Bye2iZ=wX5MbQQRsyrQQgvI&KPy5rE5nP6TsdqFC=v5FySNy9PnLFP%2Fak&vFqzXK01kZxhRS2%2F=sEOfhIQZTIt2wpC6&ElnwuDa8rhSeyCb%2F=4Na8Lsa3pm8XXHKC&ts%2BRZ3f1to0kDKTe=9rtgCHwfws56%2BkLo&sH%2FdUBFRtS%2F7HAtS=8kQjLUgGd8xiG6Dx&5usUHesurVIKS%2Fqj=auQASJf3B7vHy9QS&TbtQxf%2B3fwxdJ1Nw=L2p3mW4BkLNOwD86&eyf5XhJa%2BROultM3=NRpd%2F%2F0JzWVfjOfL&paJqz8ls1e1t1kPQ=Kys5D0LOPksACWs8&FXLLw%2FdaZO7hfpLk=TpI%2BxhHHQ86Y46hf&yTW%2FQcb1bCZj1GnX=qDgKLAKuRSBBbklh&z6i5ewmcbwUBKQVF=RZIvPMUfDLONNGs2&bAIJEdWZXYjqS4B1=eef4AphM%2BUlKP7z%2F&pjnkuTZsoYqgY0aG=MGOmFSsyzRhx7Q%2F8&TsYyyFvEwh6kQnF%2F=mQII7tLbG0iDJxFs&w5Xx%2FFzWgyCQ39FA=5inNlYi3mQKlbWnJ&sUUkwHLcLg%2BzryGR=JqqrGbDTN16Yd1vJ&gQMeQP5mFoDC%2BgwI=XuP1n%2FLEDqk0eDN7&ilIbi3%2Bo7qsb0iyR=a6KyXljSIDZRsr8R&9cripQltL%2FFUEvZi=RJhvh42QZ994O4RO&dGbVDPfNb65fCrCz=JXcAdAtH4g8KS0EZ&qORxuRt32k8ljXDl=iDy2p2Bd%2F0fj3f4t&vnwWFfg16BET%2Fbev=TbnezqPKIgKKTSsL&SumpcQV%2ByvMmZznL=Q91cOCz6yLQg2JNS&BwuyrlGOh7LJc8Dd=5hRHz3E4KJGan3hD&IaVVD%2F24Zvfw4nd2=BU14tgUKyUfUKGXG&cD25gDNzYIkq7xOM=myeZRkmE1tdBCwF%2B&SvZ4A1MeYYXOOgoI=64uwFZNL5xzaSILy&yka%2BnQME40BmuvnK=WlM0d1O6V2dkdyzZ&OMnO0rkNGLvURWqB=MZiynatckr%2FCJbbE&VphTixNyqEGSHhgl=SfzcNkB0iZSrwSzP&gb7udUlcFWuZxGKQ=tIK%2BTxgyeKzyraJt&3fpPWuOm%2FHpoCQai=8CPpKHzIhBaXuYV1&2fb%2F5kVEQdGtprx8=llQugZTnoxEzRr5X&cnT3scP3qw0egLEj=fr6LlBcGTP0bJ1Ng&IqTzrPDnj4cLHpja=ey2v0ZcL30AkPDtf&aZKUGmLVUgzyAEQ3=q3MDvL6CaEcz1JVn&y2SpDUPO4J%2BCFLJY=gdw4mtUyW67Rhynm&WXEft3ijbcyQTw08=EQkU2dEBX5PY2hC4&Yp19EP%2FVR4NpSELS=F2BSTE4dCa%2Bv9p1e&oVmDfrpy84DjckjL=P3UY7uJ56BKxlz8d&yQnXL6uMnGTFAiOo=Dw8XqTRYfK%2BvYd3Z&gA92VjvMiZE2DaRG=ylc6odA3ZfgGQvj4&VmYEMHuUa5DBI0%2Bj=iYKE8EDUw%2FQiBrbH&ilAxTLs12zxQJ5sb=liH%2Ft06Hmr4PazaO&PfXzPpupNwWFrR4t=vdzDlrpNeLZhavVn&OY2IWBZEnRpweZZ7=9Sysk4U2Rk6IIaRH&ryLaY1P4soAOJAd%2F=zORMhY2nQAL3DOwu&t29WFv1BXVnrEoBJ=WfQs44Af%2BE8GRDaj&Tqvyv7JIWCalZuM%2F=%2B1RVSlJ3nDln2HKC&YJzhvkiKgeJUCRqY=LvM6%2FDBUpUc6buEz&2DILsov5R0evIhHD=feIkJS8Q3VBGVBs1&MbTlMWUVFPBF2iGI=8RZPqqeBqS1iFQLM&gmt%2FasetG6ERsqWj=13tXsVkk3iWjvfRv&uUPTWGSDc0nYZ%2Fzo=DKVjyRG%2BPfHJRAUF&W%2BiSNvGeB1Ql6LPT=k3RAXNqD4MgyF%2BD%2B&BfBkNAZnPVN5EydY=rtMniiHr7CpthQqp&L8fEhJHafEHPodEM=OGMWmbYpYcvLKrzO&4ZEleyRp%2FwLBHiAG=aT%2BVaMxFLAHpmYz9&bFwHZKmao9DhqqUf=FQPh6tfbARUbqZtf&HeQ6QOdQk0ayTbcO=Y71hVe6Xt8BHVLcI&KPhe3fsIFn1JlGGp=tygSMtS22Ab4gNsQ&RlHKspGFIpyfl71J=uGFkVoudEngeyVRi&76m0oxfbb0Dc6QMf=6JixEHJw2uyfpaqC&UgY7%2FYq4PPeXZAcG=GDZ8onu4%2FMDPj5U9&OG2UVkwy13TfeiAE=DD6YVz0AaBdJco1v&NfRzciRkP3qGtzg1=SgMjhoBHLa%2Blrndh&%2BKNOBa5VbOrjDmJ9=jR1d2nPGWhzw8Ukb&OsMXHyTGa%2B1jnn0p=vmAGUt47CVQd7tyl&2CbBEabbzKrFbcdg=bDeAuu33wWv5ifNT&vxVBwabRUz%2FGoVJF=rpGFwarCaMaCswsD&Sim3Q6UtUwpP%2BJTf=NvWEsL3ncUSx8X4s&3bpWESNdkg4k3r3R=GhHNgJPyoDfGHbPc&YkNA24nJjK4IV9Cv=95lIk7tzzuzQh%2BLL&6zRYxCJdE4M9XIwg=ht19Dj%2BtQJe88JVr&4%2Blt2AMdd7RFREoN=AyijtjzBRQA1iuKQ&d3URA%2BfZBDM50skc=I%2BDu0EFkq0zhDBSk&PO9Kaqn7NkAmY2dC=3zlQ07eAS2V9sQO9&svDqXa6v6rACg%2Bl%2B=%2F5Z6M9Z1fWG7c%2BGH&d3EnXS4Fs%2BJ%2BdcQH=FEG0eUnf2AEla2r5&RdtCmCkLddzbU7y%2B=HOFh%2BhTWHy3q52Z9&jVlLcpIdRKmbBNzy=lE%2By9GU%2Bn5JwcJF0&COGPIiFbEPfe%2Fpqa=I%2F15DpV2J3o4dRA3&hwqYlMzNtPQd1cx9=4H5R%2BssO08v21wR8&Ro%2BfEzKVP99GOGYB=JHrcuFi6m3ZSEBRJ&MIY1xGVYFn3F%2B%2F0L=so6H4PxWQ8R3aSZp&EC%2FllNd8x0260UVU=aAtnxtoAgjXT3BjG&s6Jp1ZyxmjeqkHTC=sk4Aa3UAA6OC%2Fq90&4CEnDaORUpK%2Fcmot=X8XDg6KxUWH%2Fi6FL&NP9dfun2Ls7DarR%2F=CBIrITqNsHg9nlHm&vGfH%2FTYLO%2Bp0qhcf=4SCRDIJoXfwLbYD9&FpEh28whBZ6DkK3y=9HhBkdYyatxX1daf&RyG734LoTGyCGSzA=m4m9sqvGbp2%2BUB4m&ASXQMLpptY%2FSvgHO=z%2B%2FzMzmEzazUo%2BI7&LPFhQY0kOWLVm41m=96MDkY7TCU0a9998&f982r3J1SoBJh46B=rrHhFFS5ZNzHJgId&LlxrYvfS97bJJ6Mw=8nqJ0XFH9zembUSa&OJ3fh1QYmAFsUL1u=n8rbFz7GHZ2TuV3T&u%2FsRm9c%2FzF6OZhrx=Lr%2F7eAUJhgFDqYkQ&WaurQzJL9m%2BbXHIg=MZMK78TtEe1sac%2FH&8IeqdSYP3YHOXRkk=pEvbZ3z0BPZPT7I0&GmoSM7%2B8LtC8DS86=pVF0RoXiOMKXb5Lg&UGIPC3ZDHFI9T5p%2F=yyQFXCyxnfgGRoeu&47gTEEhAP7Zt2QM4=XwYOYn%2FyQCRpv5G3&k3hTslVXbg3UyG7w=g6qYUMSI2xQMbAS3&9S5Vby09hK8S%2BdSt=ZYuNZ9PbW8QfbuX8&4DTfvf1NnCN7FeWM=G%2FGwkGiKtFFZ%2BcVa&Rhw2Uox6goXScBk7=BZVCbv3cKPTCz3Oi&CeVK5oHOhkqx1sQh=IUo2yBJCOhK%2F%2FZcr&dAFC7G5CTQVj1oWo=vzHpGJ3sl3Q7R6wF&o7feaxFGdusID%2FcT=JeiRAiSNRWdv3euv&DdbYMiIjo%2FjziIv9=5iHreYZCTFJzJiow&%2BaaWMOlvO8%2F99kAl=9BBePwphebtfz7vV&fa%2Bmw36iN%2Be8w%2FsT=F3nfNFvP79wL2wX7&Yt55V20VczF7CZ4N=Qw6kWdo7G8VsLfCi&15kVMUTN6C%2BMlWJj=E2CX3q%2B9iO%2BtNc8O&%2B%2BsH7wur00GiwxRI=9eGfsbUgltkw3Eq8&zsKPZJU7i9wTIm%2FU=o9EAPa9Zv6UdRiZO&Jov%2BhKOkf0oAnMbv=lUmOsjzPa2WD7jsD&7lCJvJ2XTH%2FrWeGC=QwsbBe57%2FUUcZr83&l2a1VfleH9LNMFlg=UGp6%2BW4Ruqf%2BhDgz&8NrQ%2BVRkPesmkohc=p5JR3cNr9TG7Qpld&%2FZ%2FpdNgM%2BmX9KDv2=Vq0VB2ZQlGSX%2Fud7&BFfI9LWuQiI7gzhS=AwJexNIbBxGXRAiR&xd8xLhgRf1aWEi9W=rw%2FXDNMOKsNbhoiD&p4jYYe8Pz7nRM4ye=ozyuhRV9xN0p6IaN&8U9lRWTtE8UeJ7dy=keJ2HBm8mv6tgkDD&Nu01Z3FeiJD23PU2=JDp%2F8SI8s9%2F8Q8Y6&%2FMthZ6TBO4muygI%2B=7vNGDp6L%2FK%2BIKbKc&mTicsMRkRCQFzw7K=8%2BfKg%2BgWm5Vggk2K&%2F1X3kUj8QXFHzAEF=vr5KrgRPq%2B67FAMe&vd0egO1YZxNZ0OuP=EMtIqG3ujMwq3%2FIz&qEd9JzxwmvAIrE3a=HUgpuENMsICb6cTT&qu4bfaN4yv%2FbEHL%2F=k48rC2TcCMO%2Bp1xg&XlSc%2BO6F9VV63glx=y%2FuRFb5jof2Fzlvw&AJqIG9UyD91ZBhXQ=hxhaaPzReW%2BoZLLC&Lv9%2BLDj7dhP7Sfua=wzxD3h50SmdXbU3K&Hl%2FCxKFtZWr5We2R=PFw9UjywQqbAaCq1&mpE3Vi%2BAOHyFnxo1=baRmf7PGA%2F7P0dWT&l0qh3h56%2F14H0gU3=dW5v68UY5GjrHnnu&sfM9eKn9dWvoWZAc=J37pQGEGUHDff1uO&9dfoYUlhQbzZ4fAH=Ccawsmsf6BKOlRzs&YyM0Re7HOyjHBS1T=%2BbGNPECskcCW3BcR&%2B8GFcaz7f6lN7UN1=hE7qykqPK7yaF6Lo&V739PD7qwVVOZqXt=iddn7FxQetHuF%2F8r&RMiECjK3FJd3yKSt=19YtBkuAAk9%2F%2B2GY&haOZqzpwnRbqZz9h=wBGUghdiEYMl0GkF&8QwvA94DQYFDXrL1=7YhRIJg%2BQdudB9bQ&gtQqqCbtjP0MxdgJ=XYd0RnaIIbkXIpLG&B71xvvD4ZwOIw%2Fm6=X7BG7YHquElYi8XI&IiCFKRHR8eiEbMFT=V0Edwxqi%2BEwUGOEt&VbXwshvrLAwD1TiX=aOBQf14Fk4oBrxiG&uX%2Bk%2Bwm26AofQH9%2B=xnB1CuIzavHEsUNb&hyZy68sqJ%2FR%2FtCPm=42qWNjF224vuGgBj&eVKhp%2F20akiW%2F%2B4F=yT5Sx5u7iA313Urn&eT03%2B6A9lHNSEcwp=jW88iCsS9NSsbs3%2F&2Za5Hs5gkpWMtQzf=a2lTTgyDK3fDEYCt&DN4bIPb5WE7A91km=kE%2FB31IMwvxApJ39&TIUOd%2Fe816vex%2F1%2B=lR8OrOGJXIuFCxh4&YOhhaYLl%2FnXn7weZ=CbiZKwc%2FBK1xkpxv&DDpcuhNt0Ald8NFV=lsNjKxU5%2B4EGmy7R&ESWFgS9boujqGyrP=qIfiUz2lVBvKJT7V&IlzrZnq2J3VmKzR7=nLbXkND45crzbK7x&qUiBJVK8R4Xjv3rK=YIYeCRClIoe%2FLHw%2B&GZX91Tcg%2BWwYrPNy=2m1mCmoy%2BDjPzgSh&wiX03yWnyggKZzag=rxen6HxzZZ2V%2FU1y&EUM%2FkgOYZ%2FE7EN5g=w9776Ug16t1bKy3N&DpxOlOLD9a2c5f0D=eKgzxwyBgP%2FI75SL&bq7PubeC8b518gCr=d4asFrDQjm%2Fdi28K&NyEZj0%2FwumzDzy81=R%2Bnm1l2XYC%2BAVY19&7nLpuz1%2FnyjzQaFN=oSvdFGGpaAbKGlQI&Cp%2BbN7eWHnwbda2g=AxtlL0SYunBz4q4g&No%2B%2FdPVKGJSGCNZ0=3WyQdctHULTFnrKP&5e7JTUr0oRJsa1ll=Xp%2BO4AMpHS2CszUk&hXGmb1iGUggHw7cO=Z0VDLsdAwpR6HrSQ&dNAOZVY4VIukK%2Bar=SjCxN%2BezEfGp9QZA&gtHi4izbGHikGkoM=OwiV%2FaFjS9G%2FZeB0&a7nekql6kbz5uMT4=SFT4bDEZyUdXTJvA&RGAWPNoMzxCJZYJ9=g9x32aiAUJtbI137&4%2BDx9rOG8TGjxvoS=kcCPns57gthSQ3sj&VxR7YN4WhMnXwK8g=Bav6%2Bc7lmLUCJGsL&l6s3sMN812wrurpR=kiCywyFEJVhim%2FHl&lTn70UWh1PfQWOsI=nXGcLPa8Q7ZI8bVW&4hki9dvDMBSGFYPY=cJGQbHIg29nJQwWL&RP1WWxyfHCt9ObpF=WEEne2ssq5HeGk0z&36VIjqhYehBto%2FY%2B=GH91qH07vFOb%2BJKZ&hr1c0bt2560LTBhK=vmnGBV3Vgr4yVFHL&%2BwRywXzOcgVYSBbq=0MJ8%2Fg%2FbC%2FDjTsZD&%2FOeX%2Bg%2BgsN%2BXByGZ=GwcDqEqeQMUBt3c1&HoynynmOVACU82V%2B=%2F%2BPz0DuuKGEPU7vD&4u9jHGg%2FxpQN0T4x=0gmK%2F17HaXkqFcA2&SQeRuZyufuY4TvTI=0tDUEkf%2B0nJiaFwz&qjjiwjfpqEbEA6KG=Z1Y5er4DQ6ejCT3I&MOD8KL01fwhcDdsi=czXLOZAKp8eubZE%2F&DCiB3Izxx1YTt%2Bdb=1JrPMG0C5Zn6JcMP&vEKDo4Kex6YE6w6%2B=qsF8QuwWIRZo052v&mRJ8oerOMxbhEWP3=OO9wzXuGTE%2FJ1Ewv&U8Yj5TAZY8VqZzb8=AzlBw0835DoYX0vL&5RswSNk%2FIXQCdjpZ=SgEfoMM1ph%2FnxkJY&gBMXBOHEshVihlfp=OF%2B5z4ha5FGNSV08&v%2BqfVoZ%2B8Q2Ftis0=nEe7krRDt1t96E5y&t2UscPcxMmocmCag=IDN%2F3tc%2BlQqL3p1g&aOPraZHcvC0J8n4p=ZEsQxdB%2FPPfferWz&A2ImW0LDVsA80oJc=0FC1rTMOgjvxyEuj&ryhhbqvJIYaOYm%2B%2F=m1DULR0cp8rKUM8t&HE%2Be2ZlG3ef0tvGu=O5qY3Hxa%2FsjBg2z8&0CGCZTDlhiBbops2=mBsqjxAN%2FBBAcSZc&Wrt9ePRNBk100Kn%2F=IdA%2Fl9mXa%2BvOcI2F&IVnFttNYK7d%2BGOku=rQh1el1HEq65etOE&788SUGVneQz%2BN0zB=QArR8vFWOprGky%2Bh&JlNthw0t7qy7dCKG=SZEuqImohDSHnblo&YU6%2F0VNZSwe6A10G=SzWaICZ9wBRr4xW3&zusdIcfYflrt4e6X=dHsDbNOQlFJUzBEA&CbCk6eeEEzoU4TcZ=RTaISymkeMoc6xk%2B&%2BZnf14oXZRPMnG4s=Vp6RiDrldbDU8Oxx&m9vAAMIc3CevSUMM=%2BaJV%2BjBtK%2FJCwcjO&97ZzC1CGRQwftSFv=4baeLHNUGoOBmSf4&cmrBUipzA7kWalz3=Jkg2KD4pK3d%2BLYuo&9rLFjVCHk0cR%2FB%2BT=vezMWxMoEJk0oVhO&BenRceAREREkYcQ7=FxYg4xqeEO09JobJ&NxvaTjj79CoFlOC4=eF8JO9aRiBSYIVIk&EexUsTdySt%2FoBDbX=slJNj6NUNtHaG7Dc&10tRPBPqLw8p6Vff=XutVgBVYD3kEQm59&2g3Hm4krhCc1PyXw=6224P58hJxcZRGsX&BopJ1dVkBqrcIBqm=ZlHVCELoX8NcD63u&VgL%2FuCT427PcymN%2F=4xQFmwMdu1ylYbwA&01d8C84RTSbdZNjZ=DyZDzh9lqrA6j1su&oPlf8iPpZod%2Bmsmf=CPtlYp%2F2HirXbE0b&zhqjhm0emeTrtQNw=bxfsGbkZSS2wBwz%2B&GufBBZ%2FJolnXpxKc=9D5OKhujZ6dxbIUh&HaM5sMzz4j5Xsp3W=AP1nrFkeImqOqodZ&umMxO4XrZqC7fHrZ=7toXN2B%2FFtIZl%2BWC&4hcBq52yJnbsn5C2=fWxPJbRAICIXTQqr&lLFsrFKmpYlMyV%2BH=Ur82%2FgTpzBYEaz0O&GwtRI34086Y03TE3=BT2hCHhJUhktkDgC&iHVRMTgAkRvSj9%2FD=WqduxU9IPq4zhrU4&BFfUfLYZsRFmPp13=zdbp4mtY8q%2BmkKHh&yEE6MDtCbfKJ7RJk=9q%2FIZnRow4GyPLjy&hVJ0sTGcpNVPpa9v=H%2FY7Rzm9Ux0M8V51&76C7b4SNa68n84Xn=jiTCi%2BbgkucyDr3j&JUYaJHPnNuXCwXY6=c%2BX66AA6hdChgBUi&QW4w9na0elf4d7ML=K7tngI88oNyBTIIV&KzsJyYu4QpphXb1P=JPkVkrsPFmqIPAqG&YDHi2x7VB0wfC0I7=cB0nYR8kjKW6krwG&m9F%2ByIhpZA0%2FXD5b=tcvZKqytVLJl213e&GUhngvWlFhi71wQn=xPx8qrvJh0TYL3X%2B&ndHsAW8eRqS17ndY=0EwW48n2oAnxPI73&YPyr7BQi6TPZgvWo=s4e6PoqjbBGIDEOb&dUwsDlke2AlZevHd=WKo3Nsz%2Fh63Wqvgy&5EYooFhffASUmQa6=F9vwt3MI4mo3ITGO&FAHHpk3KskC3v0kI=MzMJuDXFMqS9YLQ5&rfCCsexsBTHAj1rn=r36OGsAVpxvzXv22&fdZ0X95Np%2F2gUJQK=LoyRfOM97duAiAO7&94mzkefFdY1kkaDI=WvOlFR27piEDl5Cu&qK1RrxMHKmWTTDe5=dSKZDtQEUj3R2v7Y&8996CJqdKwLz5axv=j%2BxTDZZO3TYILOtQ&c3Z4i4hM72%2FPvPIa=gVjze36SpLWTU8KP&BxzB%2FUd6N8eceoC4=BpGU0XVYHvifl4aE&BX57GZBEX1R3Q8y3=%2Fwa2MPMgUkQmW9ej&pfKKgUIADydQZ5T%2F=8ftFbFemoYj5mSqV&M7hSrPXl0ZIbV%2BD0=624MObBiZAKm2dlw&Du3pGzhno4uLS3JW=Px697Mg9nOJwFwMJ&2yxDuw8%2F0RTqTVJY=FCQor7F2k1l%2F22iL&f3c3Z9BrZ2IdEiDL=QwVf5JjIGDWSU2PM&qhK57uodbnmLEYXm=Wv3VvYwtR%2Fk8m4ak&qyTqP3ZNaM8aupsU=WAaB7NJdJ94WOdlc&BAiBNGgwihRXw9RZ=B8C0yRtsgLWKcU%2F8&2H9wDqw42AEfhcaa=aXT%2BpU3sgz2ssF6g&1J4H%2FwoB%2BtStOywW=Ele86qjwlNgjq6iH&RQEX0iEJ857Oib%2FM=WtTKrYSlqXDqAXHQ&QoMxNYGORop348Yy=oA8DFhLqzC5lRBtc&jKRRbA8toVOpFElP=yvyMVYa9L3ozINNg&mNG%2FLqAOwSCiAuHm=OPe39JeZ6FcZ1Edn&M%2FLjXMLOmpPTGY%2Fh=umRpKFlF6nvwdK38&u%2F7WepZeZxgIqHFn=GRDic3FsGWxOytEc&RBVHXkHxN0ITpGCL=ZyJPTAlbRgFnR3y9&32X%2BgMlymYCLTfPM=hwNLvq1Fuy06gp5Y&11%2FCVohrIzZ%2FhxAk=TsBQYLT13JD76K2D&rmGhsSkWle7g0Crz=ijx4Ni6VJHeLJgsO&RmfE2nl9VbHYw9I8=4k7Xh3JC%2BHwYIXtZ&ZcYEAvNNG2F0Fd9i=tUxA7rZXIDBLQXQ3&ftGQDZKlyI2jT0dB=17Uurs0YH04qvpBC&7psYXwgmrIR%2FBlSV=AhsI%2F7b%2FogC%2BBq2J&UxaX4k7svW8LyqS2=%2B8tCTyqw9LSvpwEo&7eaOwMzFhMRI4r59=5Sbm68jFV51PzaUr&RCwIPxSiPrQxrSj5=wBTUpVeIR4evU5Y7&ga62T6E4zz0JavmY=7sw4i0hbOoxJx%2F%2FQ&vDjtMZlFDs6OWWZd=XloTjmZefivjAZhZ&Ps8ASfA%2FCDa30q3h=9OGR0E4bhHbY%2BGlJ&%2BZ36oQUZ1GUbd38D=xPZGKmMTyLVIXoSQ&C5uePwnB0vqp5wCF=mshwJxJD7COEGNLP&O5E8aSmko3SRjdn7=tdZeWdmb%2Fxk2l7lH&TsfcuQ2deA9WUJLr=0iECThwL7uaQBxA6&CcMFlPtBL9SIJoP9=4DjUAUjaYH4sOwV3&8I8FcLN8suSY%2FG4k=BfI7BSLwXF%2B%2BAb4E&9RoYhue9mBxBy3id=SLj2PC48b6ZT5apx&BKDCrgaW1mzGXEoH=AVKeXQHlV0QRcVLF&TM6DK8PG7w0Vd9U6=afXx%2FN32JjxAPTWM&5p%2BlF%2BZzE73ie6fi=shCUolsTzMPIxNjE&PBdlverdqSiRE7AS=l7XxJhoiBFaQoGKB&pCQ1aXmUlVYWmK5n=DiQk0exDWk5jsBxC&UmLiQ4JRskemaKXV=smYnu6QGNYVvaj4Y&6kaXT3ZOwrXf0Y0x=A%2FgSk7L%2B%2BWbrzt6i&eFp%2FxrOrIpzrAYJq=Mx8xT4zecboXiNO7&mhSo3GYuC1NVbqZi=pBouDxi972SfL%2Fou&V%2BbSPRYXwFRbnkGZ=3hpv3A8tpWASl8DN&4RD42grl1MsK2zf%2B=EWDc%2FQ5YXHVN8k%2Fd&%2Ft5ni%2FRPH6llihjy=tMj5AHeQYqUiZRZV&9vt7iW75Clt9gulE=vjWCSMgAE7W%2BQMWq&Bv1Oy01cT2ffJ2Ex=cgJe9wTFAt2r7Jru&MuyoAbVE5Q6ztfpj=x1OdToKdsdd802bo&eE634HkuRaCI4Drr=XJM3r5v1zykY8COG&1fZhpRs7kqHygQea=VMeM0PTUgcT%2BdH%2BK&PpL2MOlxzZunoV6j=H%2BwpizKKvqBGbDr3&3ydYqezO%2FPcVLnky=ri3TeTjUQEQHnV4M&CoBA7YxgIGL3tG7G=dw3zorKiLA01Fp4r&peLAOCZu5t8ATJnD=JBz0PYS6trTUou7c&pLUIKsYAKnql2kFp=wAvDtHKl4UvKz9YB&UNNYtJjUAFwzWTzh=ncmE7Nph%2BKHr8Dnw&1l0o8RprEJl7hx8l=UeBkorYftz9f1oSK&Inv1588UcoFdBs8D=S2xk0g3p1RQ9axTr&UUD8pbMBRMECJi9B=jS3Gf8RarXB5oKzE&58it2y0Lz%2F5061mC=3zP3TdYbEvvaeleU&%2BpF430eq3X2KNOJx=JTq6F8wwaws%2B8CgK&Rj%2FFCKamvqss4ytU=KMWuHTQLALwvd1O1&M%2FHv37Kg81Vhyq1W=EpnsAuD7t7BpMbV1&YODxD2g6uYaeLykP=y0s15hIcx8frjosX&gKAAJqPsJBtJEDFw=xHcBRW7uiUOINNhN&8z980CiKcsmdD%2Fdv=B%2BpXTlu206j7fMbn&93ysEfrMVJ5bL8jw=51nbB%2F%2FmhUGLQ1pI&mUgN3Ck9bOoWkiGg=ZNyMREkZAK93n2Cb&WehaoWGEumLqC7NZ=qdM%2BOJVnq4C02Xa%2F&2rQkNtbA5Y6uoR%2FC=10jgNk1g6EsxVHC8&KQ%2Fe9B0Qnq8tZHHC=%2FNHzMqcg0zEZydsx&9zIuikqN5kd77RwZ=r8isb7VRQNySUris&e3JUinPfFz2XA1BU=T7NTNXiXHslWDqme&9j078N11Kjocmwnr=pOs7N2ikKZZrlkhO&cKLPCivWSpD%2BFmU%2F=wHJDRGksYYMpObsM&XMSy9jxTXaWz5Jx5=g3H9P8xtwZIVZnpm&Awfvt5tcScnmOAL4=fOiOQScO7QO6Piso&12KYInL4rpwvxN1l=EgKxn9qjSFgyfNPo&TuJPAB6eC3inBRJo=CEZrQjktuo%2FX5gHA&sGVm71%2BC2PIQm6kV=6dgSlEwebyFQR66U&W5izwstn6bUjpD%2F%2F=8TmGrpdgZqcZQJpj&VwpbAY5dVGJow%2FaS=wQ5jcNiSKHkPAHPy&SI9gAMTPcDGv4cEJ=8CjZeIWPz%2F8mEC%2BC&GQiBWmsLemISc8%2FZ=9CRJzUBTJ7cC1Bl7&Fi%2F3SjV7ZZ5LfTdK=LEBltBUIEhb918%2Ft&SwKX2FWTJV2WeftY=KLf8ob7E0GQjIU%2Fo&xftlozGwS%2B%2FoY%2FZu=zJOCu%2BCjUwIFq21i&3mFKp%2F1ndfveJhwH=rsAwBHNqCXhDyuqd&gns%2FTf0cqsZ6RZtf=gFlZgVK21AX%2Ftgxq&niCYJVdWRhWFg5t%2F=v6ufpl7mkW2mVmpN&cKRS84YuvDNsSofw=bRny1HGoOnCb5Xpj&RpOPjrH77%2Fn9RCLR=5Fy6gd2TDIInvNAi&GuX6pfXd92Wim40f=yT%2FmsMw36mithaA1&t8XjPT4W5VO56Pzo=CMnw%2FymnGx8H%2FrGF&Ha211lfqoT9SagG1=bxJQkztZ465ihQq2&VWDIQlCxn3Z8fqtz=CfaRZ8%2FkhMzdkq1s&0Z0WNO%2Fh%2F8%2FxYeHj=ujDeNCZWd6gGbBLm&uNPfrOeIFby4YdIv=Ct4OfRyMeCz287T6&T7OiYHGh4wK8tIXu=Bx%2BH3KCp8T3nx1No&whanFaOv2uT6temW=YoiZzR%2BrIf7WrV9p&3Tiee5zQHjvGjtKW=Gz0nUAxkzlbR9KdV&o3v9WpltgliczGtJ=rzLh1SUkZhYi0OjE&B895RMCgRZLDe3r7=XhcktTK4MPCal0K8&sGihbSaFR8zGrKFD=KzBCRbDL%2F9B8Wl%2F6&ME06WWST5gbSqpEn=rcL%2BHN8HI8crGDm6&W1McbZ8u9j5H20gV=NicVigLxxMDSfDCm&hturNjFExp6BWPUj=0pxXqaY2%2BUP1vh9c&wXfKsXemmu5suBC4=7SFKkNY%2BZTIIPn8H&Sq4gr81g0SrjDvzA=tkjLSX%2FvGMMTGVxt&N%2FAfB5PksQfr05gS=6ztFy7SCLEeYwruR&Fjk818VHzRaTBGQt=1gU5diLE0E2nE6uo&I7oUqSWGrQIcpHfP=qJ0GIxMnQowKOmDz&6ln1%2F%2BpojOa7%2BPCg=NDGp8EZui1YUItpo&DU1n66Oi5UE%2F2%2FAM=mYZv6ixnm6J3MaxR&T%2BBs4%2Fixmj3Yftua=Tzb2x8emVRDtAbHj&2AKHO%2BYYWHxB%2BZaI=cw7XptpB8Q99wlIy&j%2BACVK0PUbpKsWB6=fNwU5k4ENN7gFe6s&VHe%2BiOrfx3M9qK1y=STV8Wy%2BPnI3dBMKD&VzsAwUfHef48b%2FOl=KTRtJvV%2FXAEk9Lf9&aTnHHLwiLJbMotwv=kCfno5eA5nuB8UZT&XTULI22BTgwbZXs8=a8ig72hjzfeG4SIm&sJhDfeTIt%2B4BU5Q1=H%2B6BrUI6eeWKKTOb&u440v9rmUjtkvu7g=QjPuoCJE2Z7fH3YW&gWyAQl0K4OR7QBFz=RcN574lA0eneUDrX&wk2JFHbt5rFxea1w=qQ8uQpq3KrFApOcE&61kB8hAzzvOgtR%2B6=eVUu9B%2BV%2Bm4eNaLJ&AyFCZBt6Vnylit2h=fqCzjmPnSeeDJJVT&WJfwSoxBef6aXFoy=6L3I0sSW0SDxpMDg&qEW1Kh1BcQzGDreC=XW13u4HpOjUgRaFi&Cz2fYFtj%2Bws7UwnF=ujyy5WtQO3rxVuhG&17300MVGMR6Mimk0=EQNydFBXQWRTN%2BDz&qVjIhpnOndQpuswp=ld2ok%2FInEA9%2FRU54&Zp5gZgv1DtVz24Jz=sihlkqBc2SdjUtca&qerpEa3Y44j9L85i=sIKOIZfMKsyYcQ49&IDhhg%2BgNaYsFoN59=w2jHv%2BtAFTQftoE6&blTlumOh44GgESeT=4XWAGtDDYXASJcT%2B&KU07sZ2fMj9Zcp5Y=l%2F4%2Bip0avwme5Kr5&6Rav%2FwJLXinMr37i=IgdtLHS%2B47cqJ5t%2F&g53%2BoPEUvxvUIU4l=%2FP%2BkFQlxZCgSJMkB&tU10zTZWndII1Tja=kp76Pm84rNYNOpav&M20qaN1%2BKu2PBZ9K=2B1bET%2FZ4RXatwdn&R%2F5ostQSZCMzEY0z=WqjupcFU%2BmCt36%2B1&bndrSEyrB0vtArzi=DkzDnyG2JaN8Maf1&tFDcWlH7MjctieOT=CPjFb%2BzJ1tz9amBV&lTPFf94S6gxZMZXy=b%2FcSjeRlLacgQ%2BRB&bn%2F5d%2FWOeMR0XxHB=QA9to60oFT5KwwZr&5H5Emk5oqSEL%2BtIK=6eNOcxFV99RwBdva&5ATPrLtbF1Nf5WUx=ShLjdi6JX4OQGMrC&rPCcTo3pIvh8f4Jt=efZqHzCoTeltVTXZ&nXzeD01%2B%2FVzHhA7n=1F%2F7ZUq4jICO8i2L&MASqimsZbAG5q8pk=1%2Bb7VtjbEA%2Bebkln&J3EQKPqr6QFp%2BZUd=5TPwLFzUW9rXXydi&0JP7Tq%2Fo%2BRRV%2BgX7=4b6abq8Gqi0XJon0&rRLvO4QOrhUSGJys=Bm7V6aTjvm1It57v&eVeMpW5UCehuBdvI=Rw9pB%2FwjzwmirSYO&%2F%2B9rKf1skkOPGSFo=ExVuEBgBdepBnviB&jqIQnqPsQd17hlAw=1FIk%2FFKSeTpNitoN&0lk63Y89RpwwagKM=wGUC8dWWdoXfTUYq&G8yMJm1YDiz8Mqb5=7UiBpXiBZ9kTdiQn&axab5rOqMRpbf1yS=aPtS5l%2B984b2C%2B1v&ulHgX%2BQexHQsaPAG=iurLJE6fRQYmWPBC&wTgFXtWPjCP5QkoH=H3xrYA61F5ch3II4&Ywea4JzbzXcOHa1Q=cOXnIlQfNIzFWSgP&WHBlEgZXlMo2452c=FhVoICD%2Bs2qUo6eM&BDz3cnQyeBW5%2BKYS=tRLAozZ6TXYXT%2BXW&qayTqpdBHPJqaGV4=i%2B3c5THzaII8McqY&Lo8iGSUCS2esCqSd=Ay%2Fc4C1rIWW9xaJY&wC7BsLJEKNDUB8eX=7854a6qyZd6EAo1A&tXLD%2FDN4kPkZOtPH=MDyA5H2NBLX42oMV&mobHEMo382yRLuT7=5Mq7JaBnCdJitlq2&6Autn242hgxaIpHD=xO6KGMcgIrA%2Fn%2FaE&enR4X%2Fr7F1QuNagf=GRkZiwGUfHK3fKaN&438yiS8%2FB4aQvaaO=yLit%2BZsSCWnZi46f&TtfqQgGyCRq5sgYn=3VBi%2BIez6zdD%2BMEg&tR7WTxDJuQQjSwe7=9z6OmPpjh7H5M7qR&KRnYaCCQu7o9oYti=HxRTLn2ro1Cb7MBz&fUEB9pUZ9CMywThj=y7eozjsfyBLxXZ4Z&bEZeQ6jlHGtM5bbK=i2bna4w3Dp8VSEW%2F&cfdUa9XaMUHKRn3z=lFrHafOOzElfWBaN&0tAU3aVPTMVzd0zb=%2BeiXGRtFL22hun%2Bt&m5t2iYGvDf9YRyPm=0B1mVumNHz9Tkv99&5PWJ1Y4Wtp5qVR%2Bp=AoqvZto0Ou0tc3ll&mWE%2BdMSdfUZM5rbI=rQhnSna2v02agqs9&iKKUd02%2FXDuLjwp9=Kqyjum%2Bfq5978oTr&Dur88%2BijdjQ%2FNdTm=sewk6JQ5RJ7LJ3Ki&a%2ByHdQ7yzcNMBFfF=M32vtCxU4GwZM8LP&podgTOcIWGu7PRj1=pUNh%2FxLbKWWWUZct&Svvmm7ohkH57pmla=T2A6Ui1QQ5M9rDHR&bC7y3JYLGCUD%2BvUP=zBjHOLm8Se5LOSX0&eC90ep7aOXaPtz7g=HM%2FoGbNQ1KIs%2FijJ&xs24DlcnmJwfp3sN=qXqlZH0gbkV%2BIfjd&tGnhQdz3cKGut1KV=27qm7YaSxlLZRVB0&8WztM%2BSGrcKWSbNl=mXbV%2FKdyp97xMheh&vgmyHESzRaYBmnuc=4%2BMYd7GJVEbYDCrP&jhokW%2BC%2Bw4%2BIPnH9=mKgQuMP%2FfDsNZver&HlKiKGanMIV47uGb=%2BLWYYotQ3ICgWmOt&zg1YmBBujXGU4hRo=OeWUi5xT2zbHqRlK&HGi4WKtXldyXs0q7=FD3Xnox1QFtqLEA0&XVVPH5HS7%2FzfCQxp=%2BsyOtpv%2FZxhSlbPN&pydcPbuN5tIW%2Bf4Y=%2BklivTINgXIp5784&cD253yxiglX0BfaJ=g0MmGqL8AO1Mt9nj&AZ7LUXaBWD0m2y7a=S7jH%2BjUf9TVEj6S%2F&47cTaKub7tVjEmRj=IRvmI6aNqE642puc&Uls9JoCh2nkoluqO=DQajJEVvoF17HEdw&ZgoGQOnn7APEXWW0=j%2B5bPN2oZiznco08&VaqHCxdIrlvAncDz=w8R2x8P5ynMlACmD&mqYiMm1SOwvWG0WU=HYwETuBTkkkL405Q&HIK8h10DbtasYnma=CQLrdiVs5d2jzexk&okuQlbv67uqOF6qB=oL7fuuCJME7drCfx&k0H4%2Bve556UmjyPL=MgpYVZ%2FY4boqDPEA&GK%2FzPaIOoMQBMPUX=MgZpcYQZInZL%2BLaX&qHgnulTF8l%2Fgv4va=jP5ge%2BpXNsuO4OHd&WyOEI8eDAI5P%2Bjos=5fVQq%2B3YbrOEJtzI&uKG0GlHIjxK%2F1Uaq=pKqQtk1OlWRCKLPw&cXtwrZqqhCncfcDh=j2tnNCKhvWGPz5tZ&GqUjHf%2BGsE6%2FNBJs=y%2BlFJXWBE%2BS3eN31&17aU2VkCkC8Fs7ak=whcK6261DYIqgd%2Bn&Irc%2F8eNlHfy10qAf=nGY9VSRqWxFQiEqR&QjKbtYyCD2A9h7Zd=vo%2BpenQAQKABBZrL&5xciAzp%2FUhYYyTqW=5vnoMiEvffjdCQxD&MD1NVAR7cpS7hlcB=xYnMU%2FH1lh0aAaAZ&GdljfLNU7xGbulyW=fWeEZ5qnP7n%2Bthma&fDiYID2YRVIp2neE=SPX0VL1SSHmz6%2FrI&%2BC0uVUdMyhEQFx%2Bh=ZCMWys0Fi%2FfHWWLt&Vs%2BGxFr4PU1jNc7J=NZufDiKnXldh0HGG&kJJe2t4yDfI3o3IP=fdS2D47qvu5vsDt5&hIK6zPdUdXwxbl5D=45VORRWnamNnDPrX&gJZ1IjKCaivdlZcP=09cqloaFcsKCQ0rT&Zca9tqTzz2TY4Jh%2F=owY5%2BGfZ%2BPiAs6of&%2BuOq1LBcMwiGDu3w=eI86brgtLYJtgfDj&maUMDN%2Feox8HYmQu=9hhMWqda7ajjDGqp&IcrrWuLyhip2pca3=XdS6ZIDfhC%2BU7OCk&x3kDIhoC8mtzJhOj=vhaTi6peMd2AIKo4&InTiRmuDGdFmz85c=eEyj6pVD%2BIxkJsfa&6YsBWzNBGtpdYfyZ=0Ne6kyiw7DQxHkRo&XHac3U%2B%2Fz%2FU4sfcH=l3rjytZzj%2BRGM63l&9NCFjikDiyv0BwkB=zIVp6bLbugUteblk&9RGSD8Mc%2F6wMh5Mr=IbvU%2B82S7tbJVvgP&bkOU846c1Vr3QKMj=TROLyUwanaKDbbCt&mkV9A8CNH0nytXbz=3hz2nJHWImfyBxzV&XHKmcQYmhocOBROV=f62%2FQgKuFx1QMuNl

================================================
FILE: bench/run_aiohttp.py
================================================
from aiohttp import web
from urllib.parse import parse_qs


async def hello(request):
    return web.Response(body=b'Hello World')


async def form(request):
    data = await request.read()
    body = data.decode()
    form = parse_qs(data)
    num_fields = len(list(form.keys()))
    return web.Response(body=b'Found %d keys.' % num_fields)


app = web.Application()
app.router.add_route('GET', '/hello', hello)
app.router.add_route('POST', '/form', form)
web.run_app(app, port=8000)


================================================
FILE: bench/run_albatross.py
================================================
from albatross import Server


class Handler:
    async def on_get(self, req, res):
        res.write('Hello World')


class FormHandler:
    async def on_post(self, req, res):
        num_fields = len(list(req.form.keys()))
        res.write('Found %d keys.' % num_fields)


app = Server()
app.add_route('/hello', Handler())
app.add_route('/form', FormHandler())
app.serve(port=8000)


================================================
FILE: bench/run_flask.py
================================================
from flask import Flask
from time import sleep

app = Flask()


@app.route('/hello')
def hello():
    sleep(0.1)


================================================
FILE: bench/run_tornado.py
================================================
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello World")


class FormHandler(tornado.web.RequestHandler):

    def post(self):
        num_fields = len(list(self.request.arguments.keys()))
        self.write('Found %d keys.' % num_fields)


def make_app():
    return tornado.web.Application([
        (r'/hello', MainHandler),
        (r'/form', FormHandler),
    ])


if __name__ == '__main__':
    app = make_app()
    app.listen(8000)
    tornado.ioloop.IOLoop.current().start()


================================================
FILE: examples/basic.py
================================================
from albatross import Server
from time import time
import asyncio
try:
    import uvloop
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
except ImportError:
    pass


class TimingMiddleware:
    async def process_request(self, req, res, handler):
        req._start_time = time()

    async def process_response(self, req, res, handler):
        duration = time() - req._start_time
        print('Request took %.4fs' % duration)


class Handler:
    async def on_get(self, req, res):
        await asyncio.sleep(0.1)
        res.write('OK')


app = Server()
app.add_route('/hello/{name}', Handler())
app.add_regex_route('/.*', Handler())
app.add_middleware(TimingMiddleware())
app.serve()


================================================
FILE: setup.py
================================================
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
# To use a consistent encoding
from codecs import open
from os import path

here = path.abspath(path.dirname(__file__))

try:
    import pypandoc
    long_description = pypandoc.convert('README.md', 'rst')
except ImportError:
    long_description = open('README.md').read()


setup(
    name='albatross3',
    # Versions should comply with PEP440.  For a discussion on single-sourcing
    # the version across setup.py and the project code, see
    # https://packaging.python.org/en/latest/single_source_version.html
    version='0.6.3',
    description='A modern async python3 web framework',
    long_description=long_description,
    url='https://github.com/kespindler/albatross',
    author='Kurt Spindler',
    author_email='kespindler@gmail.com',
    license='MIT',
    classifiers=[
        # How mature is this project? Common values are
        #   3 - Alpha
        #   4 - Beta
        #   5 - Production/Stable
        'Development Status :: 3 - Alpha',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3.5',
    ],
    keywords='web http server async',
    packages=['albatross'],
    install_requires=[
        'httptools',
    ],
    extras_require={
        'ujson': ['ujson']
    },
)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/test_data_types.py
================================================
import unittest
from albatross.data_types import (
    ImmutableMultiDict, CaselessDict,
    ImmutableCaselessMultiDict
)


class ImmutableMultiDictTest(unittest.TestCase):
    def test_immutable(self):
        z = ImmutableMultiDict(**{'one': ['two', 'three']})
        with self.assertRaises(TypeError):
            z.update({})
        assert z.get_all('one') == ['two', 'three']
        assert z.get_all('two') is None


class CaselessDictTest(unittest.TestCase):
    def test_caselesss(self):
        z = CaselessDict(**{'One': 'two'})
        assert z['one'] == 'two'
        z.update({'Three': 'four'})
        assert z['three'] == 'four'
        z.update([('FIVE', 'six')])
        assert z['five'] == 'six'
        for k in z:
            assert k in z

        assert z.get('two') is None

    def test_caseless_update(self):
        z = CaselessDict(One='two')
        z.update(two=2)
        assert z['TWO'] == 2

    def test_caseless_iterable_init(self):
        z = CaselessDict([
            ('ONE', 1),
            ('TWO', 2),
            ('THREE', 3),
        ])
        assert z['one'] == 1
        assert z['tWO'] == 2
        assert z['thRee'] == 3


class ImmutableCaselessMultiDictTest(unittest.TestCase):
    def test_immutable_caseless_multi_dict(self):
        z = ImmutableCaselessMultiDict([
            ('one', 1),
            ('ONE', 'I'),
            ('tWO', 2),
            ('TwO', 'II'),
        ])

        assert z['one'] == 1, z
        assert z.get('one') == 1
        assert z.get_all('one') == [1, 'I']

        assert z['TWO'] == 2
        assert z.get('Two') == 2
        assert z.get_all('TWO') == [2, 'II']

        assert z.get('three') is None
        assert z.get_all('three') is None


================================================
FILE: tests/test_request.py
================================================
import unittest
from albatross import Request
from albatross.http_error import HTTPError
from io import BytesIO


class RequestTest(unittest.TestCase):

    def test_request(self):
        r = Request()
        r.method = 'POST'
        r.on_url(b'/hello/test?foo=baz')
        r.args = {'name': 'test'}
        r.on_header(b'CONTENT-TYPE', b'application/x-www-form-urlencoded')
        r.on_headers_complete()
        r.on_body(b'one=two')
        r.on_message_complete()
        assert r.method == 'POST'
        assert r.path == '/hello/test'
        assert r.query_string == 'foo=baz'
        assert r.form['one'] == 'two'
        assert r.form.get('one') == 'two'
        assert r.form.get('ONE') is None
        with self.assertRaises(HTTPError):
            assert r.form['ONE']

    def test_request_cookie(self):
        r = Request()
        r.on_header(b'COOKIE', b'token=bizbaz; fizzle=bizzle')
        r.on_headers_complete()
        assert r.cookies['token'] == 'bizbaz'
        assert r.cookies['fizzle'] == 'bizzle'
        assert ' fizzle' not in r.cookies

    def test_request_raw_body(self):
        r = Request()
        r.on_body(b'stream')
        assert r.raw_body.getvalue() == b'stream'

    def test_request_json(self):
        r = Request()
        r.on_header(b'Content-Type', b'application/json')
        r.on_headers_complete()
        r._parse_body(BytesIO(b'{"my":"name"}'))
        assert r.form == {'my': 'name'}


================================================
FILE: tests/test_response.py
================================================
import unittest
from albatross import Response, HTTPError
from albatross.status_codes import HTTP_301, HTTP_302


class RequestTest(unittest.TestCase):

    def test_request(self):
        r = Response()
        with self.assertRaises(HTTPError):
            r.redirect('/')
        assert r.headers['Location'] == '/'
        assert r.status_code == HTTP_302

        with self.assertRaises(HTTPError):
            r.redirect('/another', permanent=True)
        assert r.headers['Location'] == '/another'
        assert r.status_code == HTTP_301


================================================
FILE: tests/test_server.py
================================================
import unittest
import asyncio
from albatross import Server
from albatross.compat import json
from aiohttp import client
import socket
from datetime import datetime
from hashlib import md5
from time import time

BODY = b'--------------------------5969313f95a69716\r\nContent-Disposition: form-data; name="key1"\r\n\r\nvalue1\r\n--------------------------5969313f95a69716\r\nContent-Disposition: form-data; name="upload"; filename="test.txt"\r\nContent-Type: text/plain\r\n\r\nwhat a great file\n\r\n--------------------------5969313f95a69716--\r\n'  # noqa


class Handler:
    async def on_get(self, req, res):
        res.write('Hello World')

    async def on_post(self, req, res):
        name = req.form['name']
        res.write_json({'name': name})
        res.cookies['success'] = 'true'
        res.cookies['expires_at'] = ('test1', datetime.utcnow())
        res.cookies['expires_in'] = ('test1', 100)

    async def on_put(self, req, res):
        m = md5()
        m.update(req.form['upload'].value)
        res.write_json({
            'upload': req.form['upload'].filename,
            'hash': m.hexdigest(),
            'value': req.form['key1']
        })


class TimingMiddleware:
    async def process_request(self, req, res, handler):
        req._start_time = time()

    async def process_response(self, req, res, handler):
        duration = time() - req._start_time
        res.headers['Duration'] = duration


def get_free_port():
    s = socket.socket()
    s.bind(('', 0))
    port = s.getsockname()[1]
    s.close()
    return port


class ServerIntegrationTest(unittest.TestCase):

    def setUp(self):
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.server = Server()
        self.server.add_route('/hello', Handler())

        self.port = get_free_port()
        self.url = 'http://127.0.0.1:%d' % (self.port, )
        self.async_server = self.loop.run_until_complete(
            asyncio.start_server(
                self.server._handle, '127.0.0.1', self.port, loop=self.loop
            )
        )

    def tearDown(self):
        self.async_server.close()

    def request(self, method, path, data=None, headers=None):
        async def go():
            self.session = client.ClientSession(loop=self.loop)
            response = await self.session.request(
                method, self.url + path,
                data=data, headers=headers
            )
            bytes = await response.read()
            body = bytes.decode()
            self.session.close()
            return response, body
        return self.loop.run_until_complete(go())

    def test_hello_world(self):
        response, body = self.request('GET', '/hello')
        assert body == 'Hello World'

    def test_hello_world_post(self):
        response, body = self.request(
            'POST', '/hello',
            data='name=mouse', headers={
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        )
        assert body == '{"name":"mouse"}', body
        assert response.cookies['success'].value == 'true', response.cookies

    def test_hello_world_put(self):
        response, body = self.request(
            'PUT', '/hello',
            data=BODY, headers={
                'Content-Type': 'multipart/form-data; boundary=------------------------5969313f95a69716'  # noqa
            }
        )
        assert response.status == 200, response.status
        assert json.loads(body) == {
            'upload': 'test.txt',
            'hash': 'f4b099a273213d89d4161f64c05aaf13',
            'value': 'value1',
        }

    def test_malformed_boundary(self):
        response, body = self.request(
            'PUT', '/hello',
            data='name=mouse', headers={
                'Content-Type': 'multipart/form-data'
            }
        )
        assert response.status == 500, response.status

    def test_not_found(self):
        response, body = self.request('GET', '/notfound')
        assert response.status == 404

    def test_with_middleware(self):
        self.server.add_middleware(TimingMiddleware())
        response, body = self.request('GET', '/hello')
        assert float(response.headers['Duration'])

    def test_expect_continue(self):
        response, body = self.request(
            'POST', '/hello',
            data='{"name":"test"}', headers={
                'Expect': '100-continue',
                'Content-Type': 'application/json',
            }
        )
        assert response.status == 200
        assert body == '{"name":"test"}'


class FakeHandler:
    def __init__(self, n):
        self.n = n


class ServerUnitTest(unittest.TestCase):
    def setUp(self):
        self.server = Server()
        self.server.add_route('/hello/{name}', FakeHandler(1))
        self.server.add_route('/hello', FakeHandler(2))
        self.server.add_route('/hello/{another}/motd', FakeHandler(3))

    def test_simple_route(self):
        handler, args = self.server.get_handler('/hello/test')
        assert handler.n == 1
        assert args == {'name': 'test'}, args

        handler, args = self.server.get_handler('/hello')
        assert handler.n == 2
        assert args == {}

        handler, args = self.server.get_handler('/hello/test/motd')
        assert handler.n == 3
        assert args == {'another': 'test'}

        handler, args = self.server.get_handler('/hello/')
        assert handler is None
        assert args is None
Download .txt
gitextract_m4qwufff/

├── .gitignore
├── .travis.yml
├── Makefile
├── README.md
├── albatross/
│   ├── __init__.py
│   ├── compat.py
│   ├── data_types.py
│   ├── http_error.py
│   ├── request.py
│   ├── response.py
│   ├── server.py
│   └── status_codes.py
├── bench/
│   ├── benchmark.sh
│   ├── client.py
│   ├── data.txt
│   ├── run_aiohttp.py
│   ├── run_albatross.py
│   ├── run_flask.py
│   └── run_tornado.py
├── examples/
│   └── basic.py
├── setup.py
└── tests/
    ├── __init__.py
    ├── test_data_types.py
    ├── test_request.py
    ├── test_response.py
    └── test_server.py
Download .txt
SYMBOL INDEX (115 symbols across 15 files)

FILE: albatross/data_types.py
  function caseless_pairs (line 5) | def caseless_pairs(seq):
  class Immutable (line 10) | class Immutable:
    method __setitem__ (line 11) | def __setitem__(self, k, v):
    method update (line 14) | def update(self, E=None, **F):
  class ImmutableMultiDict (line 18) | class ImmutableMultiDict(Immutable, dict):
    method __getitem__ (line 19) | def __getitem__(self, k):
    method get (line 24) | def get(self, k, d=None):
    method get_all (line 29) | def get_all(self, k, d=None):
  class CaselessDict (line 35) | class CaselessDict(dict):
    method __init__ (line 36) | def __init__(self, it=None, **kwargs):
    method __contains__ (line 42) | def __contains__(self, k):
    method __getitem__ (line 45) | def __getitem__(self, k):
    method __iter__ (line 48) | def __iter__(self):
    method __setitem__ (line 52) | def __setitem__(self, k, v):
    method get (line 55) | def get(self, k, d=None):
    method update (line 60) | def update(self, other=None, **kwargs):
  class ImmutableCaselessDict (line 69) | class ImmutableCaselessDict(Immutable, CaselessDict):
  class ImmutableCaselessMultiDict (line 73) | class ImmutableCaselessMultiDict(ImmutableMultiDict, CaselessDict):
    method __init__ (line 74) | def __init__(self, it=None, **kwargs):

FILE: albatross/http_error.py
  class HTTPError (line 3) | class HTTPError(Exception):
    method __init__ (line 4) | def __init__(self, code, message=None):

FILE: albatross/request.py
  function trim_keys (line 17) | def trim_keys(d):
  class FileStorage (line 21) | class FileStorage:
    method __init__ (line 23) | def __init__(self, field_storage):
  class Request (line 28) | class Request:
    method __init__ (line 40) | def __init__(self, method=None, path=None, query_string='',
    method _parse_cookie (line 63) | def _parse_cookie(self, value):
    method _parse_form (line 67) | def _parse_form(self, body_stream):
    method _parse_body (line 79) | def _parse_body(self, body_stream):
    method on_url (line 92) | def on_url(self, url: bytes):
    method on_header (line 98) | def on_header(self, name: bytes, value: bytes):
    method on_headers_complete (line 103) | def on_headers_complete(self):
    method on_body (line 109) | def on_body(self, body: bytes):
    method on_message_complete (line 112) | def on_message_complete(self):
    method finished (line 118) | def finished(self):
    method needs_write_continue (line 122) | def needs_write_continue(self):
    method reset_state (line 125) | def reset_state(self):

FILE: albatross/response.py
  class Response (line 7) | class Response:
    method __init__ (line 16) | def __init__(self):
    method clear (line 24) | def clear(self):
    method write (line 27) | def write(self, string):
    method write_bytes (line 30) | def write_bytes(self, bytes):
    method write_json (line 33) | def write_json(self, data):
    method redirect (line 37) | def redirect(self, location, permanent=False):

FILE: albatross/server.py
  function write_cookie (line 11) | def write_cookie(writer, key, value):
  class Server (line 29) | class Server:
    method __init__ (line 36) | def __init__(self):
    method get_handler (line 41) | def get_handler(self, path):
    method add_regex_route (line 48) | def add_regex_route(self, route, handler):
    method add_route (line 53) | def add_route(self, route, handler):
    method add_middleware (line 57) | def add_middleware(self, middleware):
    method _parse_request (line 60) | async def _parse_request(self, request_reader, response_writer):
    method _route_request (line 84) | async def _route_request(self, handler, req, res):
    method _handle (line 106) | async def _handle(self, request_reader, response_writer):
    method handle_error (line 137) | def handle_error(self, res, e):
    method _write_response (line 147) | def _write_response(self, res, writer):
    method initialize (line 161) | async def initialize(self):
    method serve (line 164) | def serve(self, port=8000, host='0.0.0.0'):

FILE: bench/client.py
  function hello (line 8) | async def hello():

FILE: bench/run_aiohttp.py
  function hello (line 5) | async def hello(request):
  function form (line 9) | async def form(request):

FILE: bench/run_albatross.py
  class Handler (line 4) | class Handler:
    method on_get (line 5) | async def on_get(self, req, res):
  class FormHandler (line 9) | class FormHandler:
    method on_post (line 10) | async def on_post(self, req, res):

FILE: bench/run_flask.py
  function hello (line 8) | def hello():

FILE: bench/run_tornado.py
  class MainHandler (line 5) | class MainHandler(tornado.web.RequestHandler):
    method get (line 6) | def get(self):
  class FormHandler (line 10) | class FormHandler(tornado.web.RequestHandler):
    method post (line 12) | def post(self):
  function make_app (line 17) | def make_app():

FILE: examples/basic.py
  class TimingMiddleware (line 11) | class TimingMiddleware:
    method process_request (line 12) | async def process_request(self, req, res, handler):
    method process_response (line 15) | async def process_response(self, req, res, handler):
  class Handler (line 20) | class Handler:
    method on_get (line 21) | async def on_get(self, req, res):

FILE: tests/test_data_types.py
  class ImmutableMultiDictTest (line 8) | class ImmutableMultiDictTest(unittest.TestCase):
    method test_immutable (line 9) | def test_immutable(self):
  class CaselessDictTest (line 17) | class CaselessDictTest(unittest.TestCase):
    method test_caselesss (line 18) | def test_caselesss(self):
    method test_caseless_update (line 30) | def test_caseless_update(self):
    method test_caseless_iterable_init (line 35) | def test_caseless_iterable_init(self):
  class ImmutableCaselessMultiDictTest (line 46) | class ImmutableCaselessMultiDictTest(unittest.TestCase):
    method test_immutable_caseless_multi_dict (line 47) | def test_immutable_caseless_multi_dict(self):

FILE: tests/test_request.py
  class RequestTest (line 7) | class RequestTest(unittest.TestCase):
    method test_request (line 9) | def test_request(self):
    method test_request_cookie (line 27) | def test_request_cookie(self):
    method test_request_raw_body (line 35) | def test_request_raw_body(self):
    method test_request_json (line 40) | def test_request_json(self):

FILE: tests/test_response.py
  class RequestTest (line 6) | class RequestTest(unittest.TestCase):
    method test_request (line 8) | def test_request(self):

FILE: tests/test_server.py
  class Handler (line 14) | class Handler:
    method on_get (line 15) | async def on_get(self, req, res):
    method on_post (line 18) | async def on_post(self, req, res):
    method on_put (line 25) | async def on_put(self, req, res):
  class TimingMiddleware (line 35) | class TimingMiddleware:
    method process_request (line 36) | async def process_request(self, req, res, handler):
    method process_response (line 39) | async def process_response(self, req, res, handler):
  function get_free_port (line 44) | def get_free_port():
  class ServerIntegrationTest (line 52) | class ServerIntegrationTest(unittest.TestCase):
    method setUp (line 54) | def setUp(self):
    method tearDown (line 68) | def tearDown(self):
    method request (line 71) | def request(self, method, path, data=None, headers=None):
    method test_hello_world (line 84) | def test_hello_world(self):
    method test_hello_world_post (line 88) | def test_hello_world_post(self):
    method test_hello_world_put (line 98) | def test_hello_world_put(self):
    method test_malformed_boundary (line 112) | def test_malformed_boundary(self):
    method test_not_found (line 121) | def test_not_found(self):
    method test_with_middleware (line 125) | def test_with_middleware(self):
    method test_expect_continue (line 130) | def test_expect_continue(self):
  class FakeHandler (line 142) | class FakeHandler:
    method __init__ (line 143) | def __init__(self, n):
  class ServerUnitTest (line 147) | class ServerUnitTest(unittest.TestCase):
    method setUp (line 148) | def setUp(self):
    method test_simple_route (line 154) | def test_simple_route(self):
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (74K chars).
[
  {
    "path": ".gitignore",
    "chars": 90,
    "preview": ".idea\n__pycache__\ndist\nalbatross3.egg-info/\nbuild/\n.DS_Store\n.coverage\ncover\nbench/output\n"
  },
  {
    "path": ".travis.yml",
    "chars": 128,
    "preview": "language: python\npython:\n    - \"3.5\"\ninstall: \n    - pip install aiohttp ujson\n    - python3 setup.py install\nscript: no"
  },
  {
    "path": "Makefile",
    "chars": 294,
    "preview": "COVERAGE=98\nTEST=nosetests -s --with-coverage --cover-package=albatross --cover-branches $(TARGET)\n\nupload:\n\tgit push\n\tp"
  },
  {
    "path": "README.md",
    "chars": 1227,
    "preview": "[![Build Status](https://travis-ci.org/kespindler/albatross.svg?branch=master)](https://travis-ci.org/kespindler/albatro"
  },
  {
    "path": "albatross/__init__.py",
    "chars": 195,
    "preview": "from albatross.request import Request\nfrom albatross.response import Response\nfrom albatross.server import Server\n\nfrom "
  },
  {
    "path": "albatross/compat.py",
    "chars": 66,
    "preview": "try:\n    import ujson as json\nexcept ImportError:\n    import json\n"
  },
  {
    "path": "albatross/data_types.py",
    "chars": 2506,
    "preview": "from albatross.http_error import HTTPError\nfrom albatross.status_codes import HTTP_400\n\n\ndef caseless_pairs(seq):\n    fo"
  },
  {
    "path": "albatross/http_error.py",
    "chars": 145,
    "preview": "\n\nclass HTTPError(Exception):\n    def __init__(self, code, message=None):\n        self.status_code = code\n        self.m"
  },
  {
    "path": "albatross/request.py",
    "chars": 4072,
    "preview": "from albatross.data_types import (\n    ImmutableMultiDict,\n    ImmutableCaselessMultiDict\n)\nfrom albatross.compat import"
  },
  {
    "path": "albatross/response.py",
    "chars": 1173,
    "preview": "from albatross import status_codes\nfrom albatross.compat import json\nfrom albatross.data_types import CaselessDict\nfrom "
  },
  {
    "path": "albatross/server.py",
    "chars": 5767,
    "preview": "import re\nimport asyncio\nfrom datetime import datetime\nfrom albatross import Request, Response\nfrom albatross.status_cod"
  },
  {
    "path": "albatross/status_codes.py",
    "chars": 5536,
    "preview": "# Copied from https://github.com/falconry/falcon/blob/master/falcon/status_codes.py\nHTTP_100 = '100 Continue'\nHTTP_CONTI"
  },
  {
    "path": "bench/benchmark.sh",
    "chars": 559,
    "preview": "function run_ab() {\n  python3 run_$1.py &\n  sleep 1\n  PID=$!\n  mkdir -p output/$2\n  time ab -c 100 -n 5000 http://127.0."
  },
  {
    "path": "bench/client.py",
    "chars": 601,
    "preview": "#!/usr/local/bin/python3.5\nimport asyncio\nfrom aiohttp import ClientSession\nfrom time import time\n\nurl = 'http://localho"
  },
  {
    "path": "bench/data.txt",
    "chars": 36105,
    "preview": "B48eI7bn1r8m2SK3=vA3mIU9kvWG7CdvT&ott5c0%2B4sAn3vGb6=kvpe1naRw%2FzuXdJn&gkYceLsi5VG6h12l=9RAM1%2BxDzUi3PnN3&dBWFou2sY2fP"
  },
  {
    "path": "bench/run_aiohttp.py",
    "chars": 485,
    "preview": "from aiohttp import web\nfrom urllib.parse import parse_qs\n\n\nasync def hello(request):\n    return web.Response(body=b'Hel"
  },
  {
    "path": "bench/run_albatross.py",
    "chars": 385,
    "preview": "from albatross import Server\n\n\nclass Handler:\n    async def on_get(self, req, res):\n        res.write('Hello World')\n\n\nc"
  },
  {
    "path": "bench/run_flask.py",
    "chars": 113,
    "preview": "from flask import Flask\nfrom time import sleep\n\napp = Flask()\n\n\n@app.route('/hello')\ndef hello():\n    sleep(0.1)\n"
  },
  {
    "path": "bench/run_tornado.py",
    "chars": 569,
    "preview": "import tornado.ioloop\nimport tornado.web\n\n\nclass MainHandler(tornado.web.RequestHandler):\n    def get(self):\n        sel"
  },
  {
    "path": "examples/basic.py",
    "chars": 702,
    "preview": "from albatross import Server\nfrom time import time\nimport asyncio\ntry:\n    import uvloop\n    asyncio.set_event_loop_poli"
  },
  {
    "path": "setup.py",
    "chars": 1375,
    "preview": "# Always prefer setuptools over distutils\nfrom setuptools import setup, find_packages\n# To use a consistent encoding\nfro"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/test_data_types.py",
    "chars": 1731,
    "preview": "import unittest\nfrom albatross.data_types import (\n    ImmutableMultiDict, CaselessDict,\n    ImmutableCaselessMultiDict\n"
  },
  {
    "path": "tests/test_request.py",
    "chars": 1448,
    "preview": "import unittest\nfrom albatross import Request\nfrom albatross.http_error import HTTPError\nfrom io import BytesIO\n\n\nclass "
  },
  {
    "path": "tests/test_response.py",
    "chars": 547,
    "preview": "import unittest\nfrom albatross import Response, HTTPError\nfrom albatross.status_codes import HTTP_301, HTTP_302\n\n\nclass "
  },
  {
    "path": "tests/test_server.py",
    "chars": 5483,
    "preview": "import unittest\nimport asyncio\nfrom albatross import Server\nfrom albatross.compat import json\nfrom aiohttp import client"
  }
]

About this extraction

This page contains the full source code of the kespindler/albatross GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (69.6 KB), approximately 34.9k tokens, and a symbol index with 115 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!