Browse Source

Merge pull request #1620 from Eyepea/asyncio_reorganization

Rename API-Hour folder to AsyncIO and prepare the arrival of new AsyncIO/API-Hour daemons
Mike Smith 10 years ago
parent
commit
3e5dc0e676
57 changed files with 872 additions and 78 deletions
  1. 1 1
      .travis.yml
  2. 0 30
      frameworks/Python/API-Hour/README.md
  3. 0 14
      frameworks/Python/API-Hour/requirements.txt
  4. 0 9
      frameworks/Python/API-Hour/source_code
  5. 30 0
      frameworks/Python/AsyncIO/README.md
  6. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/.gitignore
  7. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/LICENSE
  8. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/README.rst
  9. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/default/hello
  10. 2 2
      frameworks/Python/AsyncIO/aiohttp.web/etc/hello/api_hour/gunicorn_conf.py
  11. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/hello/api_hour/logging.ini
  12. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/hello/main/main.yaml
  13. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/init.d/hello
  14. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/logrotate.d/hello
  15. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/monit/conf.d/hello
  16. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/etc/rsyslog.conf
  17. 4 8
      frameworks/Python/AsyncIO/aiohttp.web/hello/__init__.py
  18. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/endpoints/__init__.py
  19. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/endpoints/world.py
  20. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/services/__init__.py
  21. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/services/redis.py
  22. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/services/world.py
  23. 0 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/templates/fortunes.html.j2
  24. 3 0
      frameworks/Python/AsyncIO/aiohttp.web/hello/utils/__init__.py
  25. 2 1
      frameworks/Python/AsyncIO/aiohttp.web/setup.sh
  26. 9 0
      frameworks/Python/AsyncIO/aiohttp.web/source_code
  27. 27 8
      frameworks/Python/AsyncIO/benchmark_config.json
  28. 0 0
      frameworks/Python/AsyncIO/install.sh
  29. 18 0
      frameworks/Python/AsyncIO/requirements.txt
  30. 64 0
      frameworks/Python/AsyncIO/yocto_http/.gitignore
  31. 13 0
      frameworks/Python/AsyncIO/yocto_http/LICENSE
  32. 22 0
      frameworks/Python/AsyncIO/yocto_http/README.rst
  33. 3 0
      frameworks/Python/AsyncIO/yocto_http/etc/default/hello
  34. 15 0
      frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/gunicorn_conf.py
  35. 71 0
      frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/logging.ini
  36. 14 0
      frameworks/Python/AsyncIO/yocto_http/etc/hello/main/main.yaml
  37. 79 0
      frameworks/Python/AsyncIO/yocto_http/etc/init.d/hello
  38. 7 0
      frameworks/Python/AsyncIO/yocto_http/etc/logrotate.d/hello
  39. 5 0
      frameworks/Python/AsyncIO/yocto_http/etc/monit/conf.d/hello
  40. 126 0
      frameworks/Python/AsyncIO/yocto_http/etc/rsyslog.conf
  41. 72 0
      frameworks/Python/AsyncIO/yocto_http/hello/__init__.py
  42. 1 0
      frameworks/Python/AsyncIO/yocto_http/hello/endpoints/__init__.py
  43. 39 0
      frameworks/Python/AsyncIO/yocto_http/hello/endpoints/world.py
  44. 0 0
      frameworks/Python/AsyncIO/yocto_http/hello/servers/__init__.py
  45. 1 1
      frameworks/Python/AsyncIO/yocto_http/hello/servers/yocto_http.py
  46. 14 0
      frameworks/Python/AsyncIO/yocto_http/hello/services/__init__.py
  47. 45 0
      frameworks/Python/AsyncIO/yocto_http/hello/services/redis.py
  48. 54 0
      frameworks/Python/AsyncIO/yocto_http/hello/services/world.py
  49. 20 0
      frameworks/Python/AsyncIO/yocto_http/hello/templates/fortunes.html.j2
  50. 3 0
      frameworks/Python/AsyncIO/yocto_http/hello/utils/__init__.py
  51. 1 0
      frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/__init__.py
  52. 78 0
      frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/application.py
  53. 9 0
      frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/request.py
  54. 1 4
      frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/utils.py
  55. 9 0
      frameworks/Python/AsyncIO/yocto_http/setup.sh
  56. 9 0
      frameworks/Python/AsyncIO/yocto_http/source_code
  57. 1 0
      frameworks/Python/README.md

+ 1 - 1
.travis.yml

@@ -129,7 +129,7 @@ env:
     - "TESTDIR=PHP/php-zend-framework"
     - "TESTDIR=PHP/php-zend-framework"
     - "TESTDIR=PHP/php-zend-framework1"
     - "TESTDIR=PHP/php-zend-framework1"
     - "TESTDIR=PHP/phreeze"
     - "TESTDIR=PHP/phreeze"
-    - "TESTDIR=Python/API-Hour"
+    - "TESTDIR=Python/AsyncIO"
     - "TESTDIR=Python/bottle"
     - "TESTDIR=Python/bottle"
     - "TESTDIR=Python/cherrypy"
     - "TESTDIR=Python/cherrypy"
     - "TESTDIR=Python/django"
     - "TESTDIR=Python/django"

+ 0 - 30
frameworks/Python/API-Hour/README.md

@@ -1,30 +0,0 @@
-# API-Hour Benchmark Test
-
-This is the API-Hour portion of a [benchmarking tests suite](../../) 
-comparing a variety of web development platforms.
-
-The information below is specific to API-Hour. For further guidance, 
-review the [documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/). 
-Also note that there is additional information provided in 
-the [Python README](../).
-
-## Test Paths & Sources
-
-* [JSON Serialization](hello/endpoints/world.py): "/json"
-* [Single Database Query](hello/services/world.py): "/db"
-* [Multiple Database Queries](hello/services/world.py): "/queries?queries=#"*
-* [Fortunes](hello/services/world.py): "/fortunes"
-* [Database Updates](hello/services/world.py): "/updates?queries=#"*
-* [Plaintext](hello/endpoints/world.py): "/plaintext"
-
-*Replace # with an actual number.
-
-## Get Help
-
-### Community
-
-* [API-Hour Google Group](https://groups.google.com/forum/#!forum/api-hour)
-
-### Resources
-
-* [API-Hour Source Code](https://github.com/Eyepea/API-Hour)

+ 0 - 14
frameworks/Python/API-Hour/requirements.txt

@@ -1,14 +0,0 @@
-Jinja2==2.7.3
-MarkupSafe==0.23
-PyYAML==3.11
-aiohttp==0.13.1
-aiohttp-jinja2==0.0.2
-aiopg==0.5.2
-api-hour==0.6.1
-asyncio-redis==0.13.4
-gunicorn==19.1.1
-hiredis==0.1.6
-psycopg2==2.5.4
-setproctitle==1.1.8
-six==1.8.0
-ujson==1.33

+ 0 - 9
frameworks/Python/API-Hour/source_code

@@ -1,9 +0,0 @@
-./API-Hour/etc/hello/main/main.yaml
-./API-Hour/etc/hello/api_hour/logging.ini
-./API-Hour/etc/hello/api_hour/gunicorn_conf.py
-./API-Hour/hello/__init__.py
-./API-Hour/hello/hello/endpoints/__init__.py
-./API-Hour/hello/hello/endpoints/world.py
-./API-Hour/hello/hello/services/__init__.py
-./API-Hour/hello/hello/services/world.py
-./API-Hour/hello/hello/utils/__init__.py

+ 30 - 0
frameworks/Python/AsyncIO/README.md

@@ -0,0 +1,30 @@
+# AsyncIO/API-Hour Benchmark Test
+
+This is the AsyncIO/API-Hour portion of a [benchmarking tests suite](../../) 
+comparing a variety of web development platforms.
+
+The information below is specific to AsyncIO/API-Hour. For further guidance, 
+review the [documentation](http://frameworkbenchmarks.readthedocs.org/en/latest/). 
+Also note that there is additional information provided in 
+the [Python README](../).
+
+## Test Paths & Sources
+
+* [JSON Serialization](aiohttp.web/hello/endpoints/world.py): "/json"
+* [Single Database Query](aiohttp.web/hello/services/world.py): "/db"
+* [Multiple Database Queries](aiohttp.web/hello/services/world.py): "/queries?queries=#"*
+* [Fortunes](aiohttp.web/hello/services/world.py): "/fortunes"
+* [Database Updates](aiohttp.web/hello/services/world.py): "/updates?queries=#"*
+* [Plaintext](aiohttp.web/hello/endpoints/world.py): "/plaintext"
+
+*Replace # with an actual number.
+
+## Get Help
+
+### Community
+
+* [API-Hour Google Group](https://groups.google.com/forum/#!forum/api-hour)
+
+### Resources
+
+* [API-Hour Source Code](https://github.com/Eyepea/API-Hour)

+ 0 - 0
frameworks/Python/API-Hour/hello/.gitignore → frameworks/Python/AsyncIO/aiohttp.web/.gitignore


+ 0 - 0
frameworks/Python/API-Hour/hello/LICENSE → frameworks/Python/AsyncIO/aiohttp.web/LICENSE


+ 0 - 0
frameworks/Python/API-Hour/hello/README.rst → frameworks/Python/AsyncIO/aiohttp.web/README.rst


+ 0 - 0
frameworks/Python/API-Hour/hello/etc/default/hello → frameworks/Python/AsyncIO/aiohttp.web/etc/default/hello


+ 2 - 2
frameworks/Python/API-Hour/hello/etc/hello/api_hour/gunicorn_conf.py → frameworks/Python/AsyncIO/aiohttp.web/etc/hello/api_hour/gunicorn_conf.py

@@ -7,9 +7,9 @@ workers = multiprocessing.cpu_count() * 2
 if _is_travis:
 if _is_travis:
     workers = 2
     workers = 2
 
 
-bind = ['0.0.0.0:8008', '0.0.0.0:8009', '0.0.0.0:8011']
+bind = ['0.0.0.0:8080']
 keepalive = 120
 keepalive = 120
 errorlog = '-'
 errorlog = '-'
 pidfile = 'api_hour.pid'
 pidfile = 'api_hour.pid'
 pythonpath = 'hello'
 pythonpath = 'hello'
-backlog = 10240000
+backlog = 10240000

+ 0 - 0
frameworks/Python/API-Hour/hello/etc/hello/api_hour/logging.ini → frameworks/Python/AsyncIO/aiohttp.web/etc/hello/api_hour/logging.ini


+ 0 - 0
frameworks/Python/API-Hour/hello/etc/hello/main/main.yaml → frameworks/Python/AsyncIO/aiohttp.web/etc/hello/main/main.yaml


+ 0 - 0
frameworks/Python/API-Hour/hello/etc/init.d/hello → frameworks/Python/AsyncIO/aiohttp.web/etc/init.d/hello


+ 0 - 0
frameworks/Python/API-Hour/hello/etc/logrotate.d/hello → frameworks/Python/AsyncIO/aiohttp.web/etc/logrotate.d/hello


+ 0 - 0
frameworks/Python/API-Hour/hello/etc/monit/conf.d/hello → frameworks/Python/AsyncIO/aiohttp.web/etc/monit/conf.d/hello


+ 0 - 0
frameworks/Python/API-Hour/hello/etc/rsyslog.conf → frameworks/Python/AsyncIO/aiohttp.web/etc/rsyslog.conf


+ 4 - 8
frameworks/Python/API-Hour/hello/hello/__init__.py → frameworks/Python/AsyncIO/aiohttp.web/hello/__init__.py

@@ -12,7 +12,6 @@ import aiohttp_jinja2
 import api_hour
 import api_hour
 
 
 from . import endpoints
 from . import endpoints
-from . import servers
 
 
 LOG = logging.getLogger(__name__)
 LOG = logging.getLogger(__name__)
 
 
@@ -38,12 +37,10 @@ class Container(api_hour.Container):
 
 
     def make_servers(self):
     def make_servers(self):
         return [self.servers['http'].make_handler(logger=self.worker.log,
         return [self.servers['http'].make_handler(logger=self.worker.log,
-                                                  debug=self.worker.cfg.debug,
-                                                  keep_alive=self.worker.cfg.keepalive,
-                                                  access_log=self.worker.log.access_log,
-                                                  access_log_format=self.worker.cfg.access_log_format),
-                servers.yocto_http.YoctoHttpJson,
-                servers.yocto_http.YoctoHttpText]
+                                                  debug=False,
+                                                  keep_alive=0,
+                                                  access_log=None,
+                                                  access_log_format=self.worker.cfg.access_log_format)]
 
 
     @asyncio.coroutine
     @asyncio.coroutine
     def start(self):
     def start(self):
@@ -68,7 +65,6 @@ class Container(api_hour.Container):
 
 
         LOG.info('All engines ready !')
         LOG.info('All engines ready !')
 
 
-
     @asyncio.coroutine
     @asyncio.coroutine
     def stop(self):
     def stop(self):
         LOG.info('Stopping engines...')
         LOG.info('Stopping engines...')

+ 0 - 0
frameworks/Python/API-Hour/hello/hello/endpoints/__init__.py → frameworks/Python/AsyncIO/aiohttp.web/hello/endpoints/__init__.py


+ 0 - 0
frameworks/Python/API-Hour/hello/hello/endpoints/world.py → frameworks/Python/AsyncIO/aiohttp.web/hello/endpoints/world.py


+ 0 - 0
frameworks/Python/API-Hour/hello/hello/services/__init__.py → frameworks/Python/AsyncIO/aiohttp.web/hello/services/__init__.py


+ 0 - 0
frameworks/Python/API-Hour/hello/hello/services/redis.py → frameworks/Python/AsyncIO/aiohttp.web/hello/services/redis.py


+ 0 - 0
frameworks/Python/API-Hour/hello/hello/services/world.py → frameworks/Python/AsyncIO/aiohttp.web/hello/services/world.py


+ 0 - 0
frameworks/Python/API-Hour/hello/hello/templates/fortunes.html.j2 → frameworks/Python/AsyncIO/aiohttp.web/hello/templates/fortunes.html.j2


+ 3 - 0
frameworks/Python/AsyncIO/aiohttp.web/hello/utils/__init__.py

@@ -0,0 +1,3 @@
+import logging
+
+LOG = logging.getLogger(__name__)

+ 2 - 1
frameworks/Python/API-Hour/setup.sh → frameworks/Python/AsyncIO/aiohttp.web/setup.sh

@@ -5,4 +5,5 @@ export PY3=$PY3_ROOT/bin/python
 export PY3_PIP=$PY3_ROOT/bin/pip3
 export PY3_PIP=$PY3_ROOT/bin/pip3
 export PY3_API_HOUR=$PY3_ROOT/bin/api_hour
 export PY3_API_HOUR=$PY3_ROOT/bin/api_hour
 
 
-$PY3_API_HOUR -ac --chdir=hello/ --config_dir=hello/etc/hello/ hello:Container &
+cd $TROOT/aiohttp.web
+$PY3_API_HOUR -ac hello:Container &

+ 9 - 0
frameworks/Python/AsyncIO/aiohttp.web/source_code

@@ -0,0 +1,9 @@
+./etc/hello/main/main.yaml
+./etc/hello/api_hour/logging.ini
+./etc/hello/api_hour/gunicorn_conf.py
+./hello/__init__.py
+./hello/hello/endpoints/__init__.py
+./hello/hello/endpoints/world.py
+./hello/hello/services/__init__.py
+./hello/hello/services/world.py
+./hello/hello/utils/__init__.py

+ 27 - 8
frameworks/Python/API-Hour/benchmark_config.json → frameworks/Python/AsyncIO/benchmark_config.json

@@ -2,14 +2,14 @@
   "framework": "AsyncIO",
   "framework": "AsyncIO",
   "tests": [{
   "tests": [{
     "default": {
     "default": {
-      "setup_file": "setup",
+      "setup_file": "aiohttp.web/setup",
       "json_url": "/json",
       "json_url": "/json",
       "db_url": "/db",
       "db_url": "/db",
       "query_url": "/queries?queries=",
       "query_url": "/queries?queries=",
       "fortune_url": "/fortunes",
       "fortune_url": "/fortunes",
       "update_url": "/updates?queries=",
       "update_url": "/updates?queries=",
       "plaintext_url": "/plaintext",
       "plaintext_url": "/plaintext",
-      "port": 8008,
+      "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
       "classification": "Micro",
       "classification": "Micro",
       "database": "Postgres",
       "database": "Postgres",
@@ -24,12 +24,12 @@
       "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + PostgreSQL"
       "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + PostgreSQL"
     },
     },
     "redis": {
     "redis": {
-      "setup_file": "setup",
+      "setup_file": "aiohttp.web/setup",
       "db_url": "/db_redis",
       "db_url": "/db_redis",
       "query_url": "/queries_redis?queries=",
       "query_url": "/queries_redis?queries=",
       "fortune_url": "/fortunes_redis",
       "fortune_url": "/fortunes_redis",
       "update_url": "/updates_redis?queries=",
       "update_url": "/updates_redis?queries=",
-      "port": 8008,
+      "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
       "classification": "Micro",
       "classification": "Micro",
       "database": "Redis",
       "database": "Redis",
@@ -44,9 +44,28 @@
       "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + Redis"
       "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + Redis"
     },
     },
     "json": {
     "json": {
-      "setup_file": "setup",
+      "setup_file": "yocto_http/setup",
       "json_url": "/json",
       "json_url": "/json",
-      "port": 8009,
+      "port": 8080,
+      "approach": "Stripped",
+      "classification": "Platform",
+      "database": "None",
+      "framework": "AsyncIO",
+      "language": "Python",
+      "orm": "Raw",
+      "platform": "API-Hour",
+      "webserver": "Gunicorn",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "API-Hour+AsyncIO",
+      "notes": "Python 3 + API-Hour + AsyncIO"
+    },
+    "dbs": {
+      "setup_file": "yocto_http/setup",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "update_url": "/updates?queries=",
+      "port": 8081,
       "approach": "Stripped",
       "approach": "Stripped",
       "classification": "Platform",
       "classification": "Platform",
       "database": "None",
       "database": "None",
@@ -61,9 +80,9 @@
       "notes": "Python 3 + API-Hour + AsyncIO"
       "notes": "Python 3 + API-Hour + AsyncIO"
     },
     },
     "plaintext": {
     "plaintext": {
-      "setup_file": "setup",
+      "setup_file": "yocto_http/setup",
       "plaintext_url": "/plaintext",
       "plaintext_url": "/plaintext",
-      "port": 8011,
+      "port": 8082,
       "approach": "Stripped",
       "approach": "Stripped",
       "classification": "Platform",
       "classification": "Platform",
       "database": "None",
       "database": "None",

+ 0 - 0
frameworks/Python/API-Hour/install.sh → frameworks/Python/AsyncIO/install.sh


+ 18 - 0
frameworks/Python/AsyncIO/requirements.txt

@@ -0,0 +1,18 @@
+aiohttp==0.16.3
+-e git+https://github.com/Eyepea/aiohttp_jinja2.git@c9675e5c1e1ee7741b30aea8d8fbffcde016c7a0#egg=aiohttp_jinja2-master
+aiopg==0.7.0
+-e git+https://github.com/Eyepea/API-Hour.git@577abbdcbb8cc2810dad46e260b338b15db4d0e3#egg=api_hour-master
+asyncio-redis==0.13.4
+chardet==2.3.0
+gunicorn==19.3.0
+hiredis==0.2.0
+Jinja2==2.7.3
+MarkupSafe==0.23
+piprot==0.9.1
+psycopg2==2.6
+PyYAML==3.11
+requests==2.7.0
+requests-futures==0.9.5
+setproctitle==1.1.8
+six==1.9.0
+ujson==1.33

+ 64 - 0
frameworks/Python/AsyncIO/yocto_http/.gitignore

@@ -0,0 +1,64 @@
+# Created by .gitignore support plugin (hsz.mobi)
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.cache
+nosetests.xml
+coverage.xml
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# PyCharm/IDEA files
+.idea
+*.iml
+
+# Virtualenvs
+venv/
+pyvenv/

+ 13 - 0
frameworks/Python/AsyncIO/yocto_http/LICENSE

@@ -0,0 +1,13 @@
+Copyright [2015] [Eyepea Dev Team]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 22 - 0
frameworks/Python/AsyncIO/yocto_http/README.rst

@@ -0,0 +1,22 @@
+hello
+=====
+
+Install
+-------
+
+#. Follow pythonz install doc
+#. pythonz install 3.4.2
+#. cd /opt
+#. Git clone your app here
+#. cd /opt/hello/
+#. /usr/local/pythonz/pythons/CPython-3.4.2/bin/pyvenv pyvenv
+#. . pyvenv/bin/activate
+#. pip install -r requirements.txt
+#. cd /etc/init.d/ && ln -s /opt/hello/etc/init.d/hello
+#. cd /etc/default/ && ln -s /opt/hello/etc/default/hello
+#. cd /etc/monit/conf.d/ && ln -s /opt/hello/etc/monit/conf.d/hello
+#. update-rc.d hello defaults
+#. cp -a /opt/hello/etc/hello /etc/
+#. Adapt rsyslog and lograte
+#. service hello start
+#. service monit restart

+ 3 - 0
frameworks/Python/AsyncIO/yocto_http/etc/default/hello

@@ -0,0 +1,3 @@
+# Start the daemon by default, let the user disable it.
+START_DAEMON=yes
+DAEMON_ARGS="--config_dir=/etc/hello"

+ 15 - 0
frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/gunicorn_conf.py

@@ -0,0 +1,15 @@
+import multiprocessing
+import os
+
+_is_travis = os.environ.get('TRAVIS') == 'true'
+
+workers = multiprocessing.cpu_count() * 2
+if _is_travis:
+    workers = 2
+
+bind = ['0.0.0.0:8080', '0.0.0.0:8081', '0.0.0.0:8082']
+keepalive = 120
+errorlog = '-'
+pidfile = 'api_hour.pid'
+pythonpath = 'hello'
+backlog = 10240000

+ 71 - 0
frameworks/Python/AsyncIO/yocto_http/etc/hello/api_hour/logging.ini

@@ -0,0 +1,71 @@
+[formatters]
+keys=detailed,simple
+
+[handlers]
+keys=console,syslog,smtp
+
+[loggers]
+keys=root,warnings,asyncio,gunicorn,aiohttp,api_hour,hello
+
+[formatter_simple]
+format=%(name)s:%(levelname)s %(asctime)s %(module)s.py => %(message)s
+
+[formatter_detailed]
+format=%(name)s:%(levelname)s %(asctime)s %(module)s.py:%(lineno)d => %(message)s
+
+[handler_console]
+class=StreamHandler
+args=(sys.stdout,)
+formatter=detailed
+
+[handler_syslog]
+class=handlers.SysLogHandler
+args=('/dev/log', handlers.SysLogHandler.LOG_LOCAL6)
+formatter=detailed
+
+[handler_smtp]
+class=handlers.SMTPHandler
+level=WARN
+args=('127.0.0.1', '[email protected]', ['[email protected]'], 'hello error on server: hello')
+formatter=detailed
+
+[logger_root]
+level=ERROR
+handlers=console
+
+# You can add smtp in handlers list to receive e-mail alerts
+[logger_warnings]
+level=WARN
+handlers=console
+qualname=py.warnings
+propagate=0
+
+[logger_asyncio]
+level=WARN
+handlers=console
+qualname=asyncio
+propagate=0
+
+[logger_gunicorn]
+level=WARN
+handlers=console
+qualname=gunicorn
+propagate=0
+
+[logger_aiohttp]
+level=WARN
+handlers=console
+qualname=aiohttp
+propagate=0
+
+[logger_api_hour]
+level=WARN
+handlers=console
+qualname=api_hour
+propagate=0
+
+[logger_hello]
+level=WARN
+handlers=console
+qualname=hello
+propagate=0

+ 14 - 0
frameworks/Python/AsyncIO/yocto_http/etc/hello/main/main.yaml

@@ -0,0 +1,14 @@
+---
+engines:
+  pg:
+    host: 127.0.0.1
+    port: 5432
+    dbname: hello_world
+    user: benchmarkdbuser
+    password: benchmarkdbpass
+    minsize: 22
+    maxsize: 22
+  redis:
+    host: 127.0.0.1
+    port: 6379
+    poolsize: 40

+ 79 - 0
frameworks/Python/AsyncIO/yocto_http/etc/init.d/hello

@@ -0,0 +1,79 @@
+#!/bin/sh
+#
+### BEGIN INIT INFO
+# Provides:          hello
+# Required-Start:    $local_fs $remote_fs $network $syslog
+# Required-Stop:     $local_fs $remote_fs $network $syslog
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Startup daemon script for hello
+### END INIT INFO
+#
+# Author: Ludovic Gasc <[email protected]>
+set -e
+
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
+DAEMONNAME=hello
+RUNDIR=/run/$DAEMONNAME
+DAEMON=/opt/hello/${DAEMONNAME}_cli
+PIDFILE=/run/lock/${DAEMONNAME}_0
+DAEMON_ARGS=""
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Create RUNDIR if it doesn't exist
+[ -d "$RUNDIR" ] || mkdir -p "$RUNDIR"
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+[ -f /etc/default/rcS ] && . /etc/default/rcS
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+case "$1" in
+  start)
+    log_daemon_msg "Starting" "$DAEMONNAME"
+    if start-stop-daemon -b --start --pidfile $PIDFILE --startas $DAEMON -- $DAEMON_ARGS;
+    then
+        log_end_msg 0
+    else
+        log_end_msg 1
+    fi
+    ;;
+  stop)
+    log_daemon_msg "Stopping" "$DAEMONNAME"
+    if start-stop-daemon --stop --retry 5 --pidfile $PIDFILE;
+     then
+         log_end_msg 0
+     else
+         log_end_msg 1
+     fi
+    ;;
+  reload|force-reload)
+    log_daemon_msg "Reloading" "$DAEMONNAME"
+    if start-stop-daemon --stop --signal 1 --pidfile $PIDFILE --startas $DAEMON;
+     then
+         log_end_msg 0
+     else
+         log_end_msg 1
+     fi
+    ;;
+  restart)
+    $0 stop
+    $0 start
+    ;;
+  status)
+    status_of_proc -p $PIDFILE "$DAEMON" $DAEMONNAME && exit 0 || exit $?
+    ;;
+  *)
+    echo "Usage: $0 {start|stop|reload|force-reload|restart|status}"
+    exit 1
+    ;;
+esac
+
+exit 0

+ 7 - 0
frameworks/Python/AsyncIO/yocto_http/etc/logrotate.d/hello

@@ -0,0 +1,7 @@
+/var/log/hello/hello.log {
+    daily
+    rotate 15
+    compress
+    copytruncate
+    missingok
+}

+ 5 - 0
frameworks/Python/AsyncIO/yocto_http/etc/monit/conf.d/hello

@@ -0,0 +1,5 @@
+check process cache_updater with pidfile /run/lock/hello_0
+	group hello
+	start program = "/etc/init.d/hello start"
+	stop program = "/etc/init.d/hello stop"
+	if 5 restarts within 5 cycles then timeout

+ 126 - 0
frameworks/Python/AsyncIO/yocto_http/etc/rsyslog.conf

@@ -0,0 +1,126 @@
+#  /etc/rsyslog.conf	Configuration file for rsyslog.
+#
+#			For more information see
+#			/usr/share/doc/rsyslog-doc/html/rsyslog_conf.html
+
+
+#################
+#### MODULES ####
+#################
+
+$ModLoad imuxsock # provides support for local system logging
+$ModLoad imklog   # provides kernel logging support
+#$ModLoad immark  # provides --MARK-- message capability
+$SystemLogRateLimitInterval 1
+$SystemLogRateLimitBurst 1000
+
+# provides UDP syslog reception
+#$ModLoad imudp
+#$UDPServerRun 514
+
+# provides TCP syslog reception
+#$ModLoad imtcp
+#$InputTCPServerRun 514
+
+
+###########################
+#### GLOBAL DIRECTIVES ####
+###########################
+
+#
+# Use traditional timestamp format.
+# To enable high precision timestamps, comment out the following line.
+#
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+
+#
+# Set the default permissions for all log files.
+#
+$FileOwner root
+$FileGroup adm
+$FileCreateMode 0640
+$DirCreateMode 0755
+$Umask 0022
+
+#
+# Where to place spool and state files
+#
+$WorkDirectory /var/spool/rsyslog
+
+#
+# Include all config files in /etc/rsyslog.d/
+#
+$IncludeConfig /etc/rsyslog.d/*.conf
+
+
+###############
+#### RULES ####
+###############
+
+#
+# First some standard log files.  Log by facility.
+#
+auth,authpriv.*			/var/log/auth.log
+*.*;auth,authpriv.none;\
+    local6.none                 -/var/log/syslog
+#cron.*				/var/log/cron.log
+daemon.*			-/var/log/daemon.log
+kern.*				-/var/log/kern.log
+lpr.*				-/var/log/lpr.log
+mail.*				-/var/log/mail.log
+user.*				-/var/log/user.log
+
+local6.*                        /var/log/hello/hello.log
+
+#
+# Logging for the mail system.  Split it up so that
+# it is easy to write scripts to parse these files.
+#
+mail.info			-/var/log/mail.info
+mail.warn			-/var/log/mail.warn
+mail.err			/var/log/mail.err
+
+#
+# Logging for INN news system.
+#
+news.crit			/var/log/news/news.crit
+news.err			/var/log/news/news.err
+news.notice			-/var/log/news/news.notice
+
+#
+# Some "catch-all" log files.
+#
+*.=debug;\
+	auth,authpriv.none;\
+	news.none;mail.none;local7.none	-/var/log/debug
+*.=info;*.=notice;*.=warn;\
+	auth,authpriv.none;\
+	cron,daemon.none;\
+	mail,news.none;local7.none		-/var/log/messages
+
+#
+# Emergencies are sent to everybody logged in.
+#
+*.emerg				:omusrmsg:*
+
+#
+# I like to have messages displayed on the console, but only on a virtual
+# console I usually leave idle.
+#
+#daemon,mail.*;\
+#	news.=crit;news.=err;news.=notice;\
+#	*.=debug;*.=info;\
+#	*.=notice;*.=warn	/dev/tty8
+
+# The named pipe /dev/xconsole is for the `xconsole' utility.  To use it,
+# you must invoke `xconsole' with the `-file' option:
+# 
+#    $ xconsole -file /dev/xconsole [...]
+#
+# NOTE: adjust the list below, or you'll go crazy if you have a reasonably
+#      busy site..
+#
+daemon.*;mail.*;\
+	news.err;\
+	*.=debug;*.=info;\
+	*.=notice;*.=warn	|/dev/xconsole

+ 72 - 0
frameworks/Python/AsyncIO/yocto_http/hello/__init__.py

@@ -0,0 +1,72 @@
+import logging
+import asyncio
+import os
+
+import aiopg
+import jinja2
+import psycopg2.extras
+import asyncio_redis
+from asyncio_redis.protocol import HiRedisProtocol
+import api_hour
+
+from . import endpoints
+from . import servers
+from .utils import yocto_http
+
+LOG = logging.getLogger(__name__)
+
+
+class Container(api_hour.Container):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self.servers['http'] = yocto_http.Application(loop=kwargs['loop'])
+        self.servers['http'].ah_container = self # keep a reference to Container
+        # routes
+        self.servers['http'].add_route('/db', endpoints.world.db)
+        self.servers['http'].add_route('/queries', endpoints.world.queries)
+        self.servers['http'].add_route('/fortunes', endpoints.world.fortunes, content_type='text/html; charset=UTF-8')
+        self.servers['http'].add_route('/updates', endpoints.world.updates)
+        self.servers['http']['j2_env'] = jinja2.Environment(loader=jinja2.PackageLoader('hello'))
+
+    def make_servers(self):
+        return [servers.yocto_http.YoctoHttpJson,
+                self.servers['http'].handler,
+                servers.yocto_http.YoctoHttpText]
+
+    @asyncio.coroutine
+    def start(self):
+        yield from super().start()
+        LOG.info('Starting engines...')
+        self.engines['pg'] = self.loop.create_task(aiopg.create_pool(host=os.environ.get('DBHOST', self.config['engines']['pg']['host']),
+                                                                     port=int(self.config['engines']['pg']['port']),
+                                                                     sslmode='disable',
+                                                                     dbname=self.config['engines']['pg']['dbname'],
+                                                                     user=self.config['engines']['pg']['user'],
+                                                                     password=self.config['engines']['pg']['password'],
+                                                                     cursor_factory=psycopg2.extras.RealDictCursor,
+                                                                     minsize=int(self.config['engines']['pg']['minsize']),
+                                                                     maxsize=int(self.config['engines']['pg']['maxsize']),
+                                                                     loop=self.loop))
+        yield from asyncio.wait([self.engines['pg']], return_when=asyncio.ALL_COMPLETED)
+        self.engines['redis'] = yield from asyncio_redis.Pool.create(host=self.config['engines']['redis']['host'],
+                                                                     port=self.config['engines']['redis']['port'],
+                                                                     poolsize=self.config['engines']['redis']['poolsize'],
+                                                                     loop=self.loop,
+                                                                     protocol_class=HiRedisProtocol)
+
+        LOG.info('All engines ready !')
+
+    @asyncio.coroutine
+    def stop(self):
+        LOG.info('Stopping engines...')
+        if 'pg' in self.engines:
+            if self.engines['pg'].done():
+                self.engines['pg'].result().terminate()
+                yield from self.engines['pg'].result().wait_closed()
+            else:
+                yield from self.engines['pg'].cancel()
+        if 'redis' in self.engines:
+            self.engines['redis'].close()
+            yield from asyncio.sleep(1) # wait redis close connection
+        LOG.info('All engines stopped !')
+        yield from super().stop()

+ 1 - 0
frameworks/Python/AsyncIO/yocto_http/hello/endpoints/__init__.py

@@ -0,0 +1 @@
+from . import world

+ 39 - 0
frameworks/Python/AsyncIO/yocto_http/hello/endpoints/world.py

@@ -0,0 +1,39 @@
+import logging
+import asyncio
+import ujson
+
+from ..services import queries_number
+from ..services.world import get_random_record, get_random_records, update_random_records, get_fortunes
+from ..services import redis
+
+LOG = logging.getLogger(__name__)
+
[email protected]
+def db(request):
+    """Test type 2: Single database query"""
+    container = request.app.ah_container
+
+    return ujson.dumps((yield from get_random_record(container)))
+
[email protected]
+def queries(request):
+    """Test type 3: Multiple database queries"""
+    container = request.app.ah_container
+    limit = queries_number(request.params.get('queries', 1))
+
+    return ujson.dumps((yield from get_random_records(container, limit)))
+
[email protected]
+def fortunes(request):
+    """Test type 4: Fortunes"""
+    container = request.app.ah_container
+    template = request.app['j2_env'].get_template('fortunes.html.j2')
+    return template.render({'fortunes': (yield from get_fortunes(container))})
+
[email protected]
+def updates(request):
+    """Test type 5: Database updates"""
+    container = request.app.ah_container
+    limit = queries_number(request.params.get('queries', 1))
+
+    return ujson.dumps((yield from update_random_records(container, limit)))

+ 0 - 0
frameworks/Python/API-Hour/hello/hello/servers/__init__.py → frameworks/Python/AsyncIO/yocto_http/hello/servers/__init__.py


+ 1 - 1
frameworks/Python/API-Hour/hello/hello/servers/yocto_http.py → frameworks/Python/AsyncIO/yocto_http/hello/servers/yocto_http.py

@@ -2,7 +2,7 @@ import asyncio
 
 
 import ujson
 import ujson
 
 
-from ..utils import generate_http_response
+from ..utils.yocto_http.utils import generate_http_response
 
 
 class YoctoHttpJson(asyncio.Protocol):
 class YoctoHttpJson(asyncio.Protocol):
     def connection_made(self, transport):
     def connection_made(self, transport):

+ 14 - 0
frameworks/Python/AsyncIO/yocto_http/hello/services/__init__.py

@@ -0,0 +1,14 @@
+from . import world
+
+# get from Django hello application
+def queries_number(number):
+    try:
+        queries = int(number)
+    except Exception:
+        queries = 1
+    if queries < 1:
+        queries = 1
+    if queries > 500:
+        queries = 500
+    return queries
+

+ 45 - 0
frameworks/Python/AsyncIO/yocto_http/hello/services/redis.py

@@ -0,0 +1,45 @@
+from operator import itemgetter
+import asyncio
+from random import randint
+
+
[email protected]
+def get_random_record(container):
+    idx = randint(1, 10000)
+    random_number = yield from container.engines['redis'].get('world:%i' % idx)
+    return {'Id': idx, 'RandomNumber': random_number}
+
[email protected]
+def get_random_records(container, limit):
+    results = []
+    for i in range(limit):
+        idx = randint(1, 10000)
+        random_number = yield from container.engines['redis'].get('world:%i' % idx)
+        results.append({'Id': idx, 'RandomNumber': random_number})
+
+    return results
+
[email protected]
+def update_random_records(container, limit):
+    results = []
+    for i in range(limit):
+        idx = randint(1, 10000)
+        random_number = yield from container.engines['redis'].get('world:%i' % idx)
+        yield from container.engines['redis'].set('world:%i' % idx, str(randint(1, 10000)))
+        results.append({'Id': idx, 'RandomNumber': random_number})
+    return results
+
[email protected]
+def get_fortunes(container):
+    results = []
+    list_reply = yield from container.engines['redis'].lrange('fortunes')
+    fortunes = yield from list_reply.aslist()
+    i = 1
+    for fortune in fortunes:
+        results.append({'id': i, 'message': fortune})
+        i += 1
+
+    results.append({'id': 0, 'message': 'Additional fortune added at request time.'})
+    results.sort(key=itemgetter('message'))
+
+    return results

+ 54 - 0
frameworks/Python/AsyncIO/yocto_http/hello/services/world.py

@@ -0,0 +1,54 @@
+import asyncio
+from random import randint
+from operator import itemgetter
+
[email protected]
+def get_random_record(container):
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            world = yield from cur.fetchone()
+    return world
+
[email protected]
+def get_random_records(container, limit):
+    pg = yield from container.engines['pg']
+    results = []
+    with (yield from pg.cursor()) as cur:
+        for i in range(limit):
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            results.append((yield from cur.fetchone()))
+
+    return results
+
[email protected]
+def update_random_records(container, limit):
+    results = []
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+        for i in range(limit):
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            world = yield from cur.fetchone()
+            yield from cur.execute('UPDATE world SET randomnumber=%(random_number)s WHERE id=%(idx)s',
+                                   {'random_number': randint(1, 10000), 'idx': world['Id']})
+            results.append(world)
+    return results
+
[email protected]
+def get_fortunes(container):
+    pg = yield from container.engines['pg']
+
+    with (yield from pg.cursor()) as cur:
+        yield from cur.execute('SELECT * FROM fortune')
+        fortunes = yield from cur.fetchall()
+
+    fortunes.append({'id': 0, 'message': 'Additional fortune added at request time.'})
+
+    fortunes.sort(key=itemgetter('message'))
+
+    return fortunes

+ 20 - 0
frameworks/Python/AsyncIO/yocto_http/hello/templates/fortunes.html.j2

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Fortunes</title>
+</head>
+<body>
+<table>
+    <tr>
+        <th>id</th>
+        <th>message</th>
+    </tr>
+    {% for fortune in fortunes %}
+        <tr>
+            <td>{{ fortune['id']|e }}</td>
+            <td>{{ fortune['message']|e }}</td>
+        </tr>
+    {% endfor %}
+</table>
+</body>
+</html>

+ 3 - 0
frameworks/Python/AsyncIO/yocto_http/hello/utils/__init__.py

@@ -0,0 +1,3 @@
+import logging
+
+LOG = logging.getLogger(__name__)

+ 1 - 0
frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/__init__.py

@@ -0,0 +1 @@
+from .application import Application

+ 78 - 0
frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/application.py

@@ -0,0 +1,78 @@
+import asyncio
+import logging
+from collections import OrderedDict
+
+from .request import Request
+from .utils import generate_http_response
+
+log = logging.getLogger(__name__)
+
+class Application(dict):
+
+    def __init__(self, default_encoding='utf-8', decode_headers=False, loop=None):
+        super(Application, self).__init__()
+        self.default_encoding = default_encoding
+        if loop is None:
+            loop = asyncio.get_event_loop()
+        self.decode_headers = decode_headers
+        self.loop = loop
+        self._route = OrderedDict()
+
+    def add_route(self, path, endpoint, content_type='application/json'):
+        assert callable(endpoint), endpoint
+        if not asyncio.iscoroutinefunction(endpoint):
+            endpoint = asyncio.coroutine(endpoint)
+        endpoint.content_type = content_type
+        self._route[path] = endpoint
+
+    @asyncio.coroutine
+    def handler(self, reader, writer):
+        # while True:
+            buffer = b''
+            while b'\r\n\r\n' not in buffer:
+                buffer += yield from reader.read(100)
+            lines = buffer[:-2].decode(self.default_encoding).split('\r\n')
+
+            url = lines[0].split(' ')[1].split('?')
+            path = url[0]
+            params = OrderedDict()
+            if len(url) == 2:
+                k, v = url[1].split('=', 1)  # @TODO: support several parameters
+                params[k] = v
+
+            # log.info('Received HTTP request from %r for "%s" route',
+            #          writer.get_extra_info('peername'),
+            #          uri)
+
+            headers = {}
+            if self.decode_headers:
+                for line in lines:
+                    k, v = line.split(': ', 1)
+                    headers[k] = v
+                log.debug("HTTP Headers: %r",
+                          headers)
+
+            if path in self._route:
+                request = Request(app=self,
+                                  path=path,
+                                  params=params,
+                                  headers=headers,
+                                  reader=reader, writer=writer,
+                                  encoding=self.default_encoding)
+                try:
+                    response = yield from self._route[path](request)
+                    writer.write(generate_http_response(response, content_type=self._route[path].content_type))
+                    try:
+                        yield from writer.drain()
+                    except ConnectionError:
+                        pass
+                except Exception as e:
+                    log.exception(e)
+            else:
+                log.error('No route for the request "%s"', path)
+                writer.write(generate_http_response(''))
+                try:
+                    yield from writer.drain()
+                except ConnectionError:
+                    pass
+            writer.close()

+ 9 - 0
frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/request.py

@@ -0,0 +1,9 @@
+class Request:
+    def __init__(self, app, path, params, headers, reader, writer, encoding='utf-8'):
+        self.app = app
+        self.path = path
+        self.params = params
+        self.headers = headers
+        self.reader = reader
+        self.writer = writer
+        self.encoding = encoding

+ 1 - 4
frameworks/Python/API-Hour/hello/hello/utils/__init__.py → frameworks/Python/AsyncIO/yocto_http/hello/utils/yocto_http/utils.py

@@ -1,14 +1,11 @@
-import logging
 from wsgiref.handlers import format_date_time
 from wsgiref.handlers import format_date_time
 
 
-LOG = logging.getLogger(__name__)
-
 def generate_http_response(payload, content_type='application/json'):
 def generate_http_response(payload, content_type='application/json'):
     return ("""HTTP/1.1 200 OK
     return ("""HTTP/1.1 200 OK
 CONTENT-TYPE: %s
 CONTENT-TYPE: %s
 CONTENT-LENGTH: %s
 CONTENT-LENGTH: %s
 CONNECTION: keep-alive
 CONNECTION: keep-alive
 DATE: %s
 DATE: %s
-SERVER: yocto_http/0.0.1
+SERVER: yocto_http/0.0.2
 
 
 %s""" % (content_type, len(payload), format_date_time(None), payload)).encode('utf-8')
 %s""" % (content_type, len(payload), format_date_time(None), payload)).encode('utf-8')

+ 9 - 0
frameworks/Python/AsyncIO/yocto_http/setup.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+export PY3_ROOT=$IROOT/py3
+export PY3=$PY3_ROOT/bin/python
+export PY3_PIP=$PY3_ROOT/bin/pip3
+export PY3_API_HOUR=$PY3_ROOT/bin/api_hour
+
+cd $TROOT/yocto_http
+$PY3_API_HOUR -ac hello:Container &

+ 9 - 0
frameworks/Python/AsyncIO/yocto_http/source_code

@@ -0,0 +1,9 @@
+./etc/hello/main/main.yaml
+./etc/hello/api_hour/logging.ini
+./etc/hello/api_hour/gunicorn_conf.py
+./hello/__init__.py
+./hello/hello/endpoints/__init__.py
+./hello/hello/endpoints/world.py
+./hello/hello/services/__init__.py
+./hello/hello/services/world.py
+./hello/hello/utils/__init__.py

+ 1 - 0
frameworks/Python/README.md

@@ -16,6 +16,7 @@ For further guidance, review the
 ### Python Experts
 ### Python Experts
 
 
 * INADA Naoki (@methane) -- Expert of Python and Python's MySQL driver.
 * INADA Naoki (@methane) -- Expert of Python and Python's MySQL driver.
+* Ludovic Gasc (@GMLudo) -- Expert of AsyncIO.
 
 
 ### [Python Community](https://www.python.org/community/)
 ### [Python Community](https://www.python.org/community/)