app.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #!/usr/bin/env python
  2. import sys
  3. import spyne.const
  4. spyne.const.MIN_GC_INTERVAL = float('inf')
  5. from lxml import html
  6. from random import randint, shuffle, choice
  7. from contextlib import closing
  8. from email.utils import formatdate
  9. from neurons import TableModel, Application
  10. from neurons.daemon import ServiceDefinition, HttpServer, StaticFileServer
  11. from neurons.daemon.main import Bootstrapper
  12. from spyne import Integer32, Unicode, rpc, ServiceBase, Integer, Array, Any
  13. from spyne.protocol.html import HtmlCloth
  14. from spyne.protocol.http import HttpRpc
  15. from spyne.protocol.json import JsonDocument
  16. from spyne.server.wsgi import WsgiApplication
  17. if sys.version_info[0] == 3:
  18. xrange = range
  19. _is_pypy = hasattr(sys, 'pypy_version_info')
  20. DBDRIVER = 'postgresql+psycopg2cffi' if _is_pypy else 'postgresql+psycopg2'
  21. DBHOST = 'tfb-database'
  22. # models
  23. class DbSessionManager(object):
  24. def __init__(self, config):
  25. self.session = config.get_main_store().Session()
  26. def close(self, with_err):
  27. self.session.close()
  28. class DbConnectionManager(object):
  29. def __init__(self, config):
  30. self.conn = config.get_main_store().engine.connect()
  31. def close(self, with_err):
  32. self.conn.close()
  33. class World(TableModel):
  34. __tablename__ = "world"
  35. _type_info = [
  36. ('id', Integer32(primary_key=True)),
  37. ('randomNumber', Integer32(sqla_column_args=dict(name='randomnumber'))),
  38. ]
  39. T_INDEX = html.fromstring(open('cloths/index.html', 'rb').read())
  40. class Fortune(TableModel):
  41. __tablename__ = "fortune"
  42. id = Integer32(primary_key=True)
  43. message = Unicode
  44. outprot_plain = HttpRpc(mime_type='text/plain')
  45. class TfbSimpleService(ServiceBase):
  46. @rpc(_returns=Any)
  47. def json(ctx):
  48. ctx.transport.add_header('Date', formatdate(usegmt=True))
  49. return dict(message=u'Hello, World!')
  50. @rpc(_returns=Any)
  51. def plaintext(ctx):
  52. """Test 6: Plaintext"""
  53. ctx.out_protocol = outprot_plain
  54. return b'Hello, World!'
  55. def _force_int(v):
  56. try:
  57. return min(500, max(int(v), 1))
  58. except:
  59. return 1
  60. NumQueriesType = Any(sanitizer=_force_int)
  61. class TfbOrmService(ServiceBase):
  62. @rpc(_returns=World)
  63. def db(ctx):
  64. retval = ctx.udc.session.query(World).get(randint(1, 10000))
  65. return retval
  66. @rpc(NumQueriesType, _returns=Array(World))
  67. def dbs(ctx, queries):
  68. if queries is None:
  69. queries = 1
  70. q = ctx.udc.session.query(World)
  71. return [q.get(randint(1, 10000)) for _ in xrange(queries)]
  72. @rpc(_returns=Array(Fortune, html_cloth=T_INDEX), _body_style='out_bare')
  73. def fortunes(ctx):
  74. # This is normally specified at the application level as it's a good
  75. # practice to group rpc endpoints with similar return types under the
  76. # same url fragment. eg. https://example.com/api/json
  77. ctx.out_protocol = HtmlCloth()
  78. ctx.outprot_ctx = ctx.out_protocol.get_context(ctx, ctx.transport)
  79. fortunes = ctx.udc.session.query(Fortune).all()
  80. fortunes.append(
  81. Fortune(id=0, message=u"Additional fortune added at request time.")
  82. )
  83. fortunes.sort(key=lambda x: x.message)
  84. return fortunes
  85. @rpc(NumQueriesType, _returns=Array(World))
  86. def updates(ctx, queries):
  87. """Test 5: Database Updates"""
  88. if queries is None:
  89. queries = 1
  90. retval = []
  91. q = ctx.udc.session.query(World)
  92. for id in (randint(1, 10000) for _ in xrange(queries)):
  93. world = q.get(id)
  94. world.randomNumber = randint(1, 10000)
  95. retval.append(world)
  96. ctx.udc.session.commit()
  97. return retval
  98. class TfbRawService(ServiceBase):
  99. @rpc(_returns=World)
  100. def dbraw(ctx):
  101. conn = ctx.udc.conn
  102. wid = randint(1, 10000)
  103. return conn.execute(
  104. "SELECT id, randomNumber FROM world WHERE id = %s", wid) \
  105. .fetchone()
  106. # returning both Any+dict or ObjectMarker+ListOfLists works
  107. @rpc(NumQueriesType, _returns=Any)
  108. def dbsraw(ctx, queries):
  109. if queries is None:
  110. queries = 1
  111. retval = []
  112. conn = ctx.udc.conn
  113. for i in xrange(queries):
  114. wid = randint(1, 10000)
  115. result = conn.execute(
  116. "SELECT id, randomNumber FROM world WHERE id = %s", wid) \
  117. .fetchone()
  118. retval.append(dict(id=result[0], randomNumber=result[1]))
  119. return retval
  120. @rpc(_returns=Array(Fortune, html_cloth=T_INDEX), _body_style='out_bare')
  121. def fortunesraw(ctx):
  122. # This is normally specified at the application level as it's a good
  123. # practice to group rpc endpoints with similar return types under the
  124. # same url fragment. eg. https://example.com/api/json
  125. ctx.out_protocol = HtmlCloth()
  126. ctx.outprot_ctx = ctx.out_protocol.get_context(ctx, ctx.transport)
  127. res = ctx.udc.conn.execute("SELECT id, message FROM fortune")
  128. fortunes = res.fetchall()
  129. fortunes.append(Fortune(
  130. id=0,
  131. message=u"Additional fortune added at request time."
  132. ))
  133. fortunes.sort(key=lambda x: x.message)
  134. return fortunes
  135. @rpc(NumQueriesType, _returns=Any)
  136. def updatesraw(ctx, queries):
  137. """Test 5: Database Updates"""
  138. if queries is None:
  139. queries = 1
  140. conn = ctx.udc.conn
  141. ids = [randint(1, 10000) for _ in xrange(queries)]
  142. retval = []
  143. for i in ids:
  144. wid, rn = conn.execute(
  145. "SELECT id, randomNumber FROM world WHERE id=%s", i) \
  146. .fetchone()
  147. rn = randint(1, 10000)
  148. retval.append(dict(id=wid, randomNumber=rn))
  149. conn.execute("UPDATE World SET randomNumber=%s WHERE id=%s",
  150. rn, wid)
  151. return retval
  152. def _on_method_call_db_sess(ctx):
  153. ctx.transport.add_header('Date', formatdate(usegmt=True))
  154. ctx.udc = DbSessionManager(ctx.app.config)
  155. def _on_method_call_db_conn(ctx):
  156. ctx.transport.add_header('Date', formatdate(usegmt=True))
  157. ctx.udc = DbConnectionManager(ctx.app.config)
  158. TfbRawService.event_manager.add_listener("method_call", _on_method_call_db_conn)
  159. TfbOrmService.event_manager.add_listener("method_call", _on_method_call_db_sess)
  160. def init_app(config):
  161. subconfig = config.services['root']
  162. app = Application(
  163. [TfbOrmService, TfbRawService, TfbSimpleService],
  164. tns='http://techempower.com/benchmarks/Python/Spyne',
  165. in_protocol=HttpRpc(),
  166. out_protocol=JsonDocument(),
  167. config=config,
  168. )
  169. if subconfig.subapps is None:
  170. subconfig.subapps = {}
  171. subconfig.subapps.update({'': app})
  172. return subconfig.gen_site()
  173. def init(config):
  174. return {
  175. 'root': ServiceDefinition(
  176. init=init_app,
  177. default=HttpServer(
  178. type='tcp4',
  179. host='127.0.0.1',
  180. port=8080,
  181. ),
  182. ),
  183. }
  184. def parse_config(argv):
  185. from neurons.daemon.main import boot
  186. retcode, config = boot('tfb', argv, init, bootstrapper=TfbBootstrap)
  187. if retcode is not None:
  188. sys.exit(retcode)
  189. return config
  190. def gen_wsgi_app():
  191. config = parse_config([])
  192. app = config.services['root'].subapps[''].app
  193. return WsgiApplication(app)
  194. words = 'some random words for you and me somebody else if then the'.split()
  195. class TfbBootstrap(Bootstrapper):
  196. # noinspection PyUnresolvedReferences
  197. def after_tables(self, config):
  198. print("Generating data...")
  199. with closing(config.get_main_store().Session()) as session:
  200. ints = list(range(10000))
  201. shuffle(ints)
  202. for _ in range(10000):
  203. session.add(World(randomNumber=ints.pop()))
  204. for _ in range(100):
  205. session.add(Fortune(
  206. message=' '.join([choice(words)
  207. for _ in range(randint(3, 10))])
  208. ))
  209. session.commit()
  210. if __name__ == '__main__':
  211. parse_config(sys.argv)
  212. else:
  213. application = gen_wsgi_app()