~sirn/fanboi2

ref: f81c2510c0021cbb79023527579b155b46bf0581 fanboi2/fanboi2/__init__.py -rw-r--r-- 5.7 KiB
f81c2510Kridsada Thanabulpong Setup application in provisioning and PyPy3 support. 7 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import hashlib
from functools import lru_cache
from IPy import IP
from pyramid.config import Configurator
from pyramid.exceptions import NotFound
from pyramid.path import AssetResolver
from pyramid.view import append_slash_notfound_view
from pyramid_beaker import session_factory_from_settings
from sqlalchemy.engine import engine_from_config
from .cache import cache_region
from .models import DBSession, Base, redis_conn, identity
from .serializers import add_serializer_adapters
from .utils import akismet, json_renderer, dnsbl


def remote_addr(request):
    """Similar to Pyramid's :attr:`request.remote_addr` but will fallback
    to ``HTTP_X_FORWARDED_FOR`` when ``REMOTE_ADDR`` is either a private
    address or a loopback address.

    :param request: A :class:`pyramid.request.Request` object.

    :type request: pyramid.request.Request
    :rtype: str
    """
    ipaddr = IP(request.environ.get('REMOTE_ADDR', '255.255.255.255'))
    if ipaddr.iptype() == "PRIVATE":
        return request.environ.get('HTTP_X_FORWARDED_FOR', str(ipaddr))
    return str(ipaddr)


def route_name(request):
    """Returns :attr:`name` of current :attr:`request.matched_route`.

    :param request: A :class:`pyramid.request.Request` object.

    :type request: pyramid.request.Request
    :rtype: str
    """
    return request.matched_route.name


@lru_cache(maxsize=10)
def _get_asset_hash(path):
    """Returns an MD5 hash of the given assets path.

    :param path: An asset specification to the asset file.

    :type param: str
    :rtype: str
    """
    if ':' in path:
        package, path = path.split(':')
        resolver = AssetResolver(package)
    else:
        resolver = AssetResolver()
    fullpath = resolver.resolve(path).abspath()
    md5 = hashlib.md5()
    with open(fullpath, 'rb') as f:
        for chunk in iter(lambda: f.read(128 * md5.block_size), b''):
            md5.update(chunk)
    return md5.hexdigest()


def tagged_static_path(request, path, **kwargs):
    """Similar to Pyramid's :meth:`request.static_path` but append first 8
    characters of file hash as query string ``h`` to it forcing proxy server
    and browsers to expire cache immediately after the file is modified.

    :param request: A :class:`pyramid.request.Request` object.
    :param path: An asset specification to the asset file.
    :param kwargs: Arguments to pass to :meth:`request.static_path`.

    :type request: pyramid.request.Request
    :type path: str
    :type kwargs: dict
    :rtype: str
    """
    kwargs['_query'] = {'h': _get_asset_hash(path)[:8]}
    return request.static_path(path, **kwargs)


def configure_components(cfg):  # pragma: no cover
    """Configure the application components e.g. database connection.

    :param cfg: A configuration :type:`dict`.

    :type cfg: dict
    :rtype: None
    """
    # BUG: configure_components should be called after pyramid.Configurator
    # in order to prevent an importlib bug to cause pkg_resources to fail.
    # Tasks are imported here because of the same reason (Celery uses
    # importlib internally.)
    #
    # This bug only applies to Python 3.2.3 only.
    from .tasks import celery, configure_celery
    engine = engine_from_config(cfg, 'sqlalchemy.')
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    redis_conn.from_url(cfg['redis.url'])
    celery.config_from_object(configure_celery(cfg))
    identity.configure_tz(cfg['app.timezone'])
    akismet.configure_key(cfg['akismet.key'])
    dnsbl.configure_providers(cfg['dnsbl.providers'])
    cache_region.configure_from_config(cfg, 'dogpile.')
    cache_region.invalidate()


def configure_views(config):  # pragma: no cover
    """Add views and routes to Pyramid configuration.

    :param config: A configuration :class:`pyramid.config.Configurator`.

    :type config: pyramid.config.Configurator
    :rtype: None
    """
    config.add_static_view('static', 'static', cache_max_age=3600)

    # views.api
    config.add_route('api_root',         '/api/')
    config.add_route('api_boards',       '/api/1.0/boards/')
    config.add_route('api_board',        '/api/1.0/boards/{board:\w+}/')
    config.add_route('api_board_topics', '/api/1.0/boards/{board:\w+}/topics/')
    config.add_route('api_topic',        '/api/1.0/topics/{topic:\d+}/')
    config.add_route('api_topic_posts',  '/api/1.0/topics/{topic:\d+}/posts/')
    config.add_route('api_topic_posts_scoped',
                     '/api/1.0/topics/{topic:\d+}/posts/{query}/')

    # views.pages
    config.add_route('root',         '/')
    config.add_route('board',        '/{board:\w+}/')
    config.add_route('board_all',    '/{board:\w+}/all/')
    config.add_route('board_new',    '/{board:\w+}/new/')
    config.add_route('topic',        '/{board:\w+}/{topic:\d+}/')
    config.add_route('topic_scoped', '/{board:\w+}/{topic:\d+}/{query}/')

    # Fallback
    config.add_view(append_slash_notfound_view, context=NotFound)
    config.scan()


def main(global_config, **settings):  # pragma: no cover
    """This function returns a Pyramid WSGI application.

    :param global_config: A :type:`dict` containing global config.
    :param settings: A :type:`dict` containing values from INI.

    :type global_config: dict
    :type settings: dict
    :rtype: pyramid.router.Router
    """
    session_factory = session_factory_from_settings(settings)
    config = Configurator(settings=settings)
    config.include('pyramid_mako')
    configure_components(settings)

    config.set_session_factory(session_factory)
    config.set_request_property(remote_addr)
    config.set_request_property(route_name)
    config.add_request_method(tagged_static_path)

    add_serializer_adapters(json_renderer)
    config.add_renderer('json', json_renderer)
    configure_views(config)

    return config.make_wsgi_app()