├── setup.cfg ├── .gitignore ├── examples ├── 7_oauth2.config ├── 6_basic_auth.config ├── 2_logging.config ├── 1_simple.py ├── views │ └── upload.html ├── upload.py ├── 4_params.py ├── 3_sessions.py ├── 7_oauth2.py ├── 8_gevent.py ├── cgevent.py ├── 2_logging.py ├── basic_auth.py ├── 5_custom_auth.py ├── 6_basic_auth.py ├── cgevent.config └── upload.config ├── setup.py ├── .github └── workflows │ └── pythonpublish.yml ├── LICENSE ├── security_note.md ├── README.md └── canister.py /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | __pycache__ 3 | canister.egg-info 4 | examples/logs -------------------------------------------------------------------------------- /examples/7_oauth2.config: -------------------------------------------------------------------------------- 1 | [canister] 2 | 3 | # OAuth2 4 | auth_jwt_client_id = alice 5 | auth_jwt_encoding = clear 6 | auth_jwt_secret = secret -------------------------------------------------------------------------------- /examples/6_basic_auth.config: -------------------------------------------------------------------------------- 1 | [canister] 2 | 3 | # Basic auth 4 | auth_basic_username = alice 5 | auth_basic_encryption = clear 6 | auth_basic_password = my-secret 7 | -------------------------------------------------------------------------------- /examples/2_logging.config: -------------------------------------------------------------------------------- 1 | [canister] 2 | 3 | # The logs directory, if commented, output will be redirected to the console 4 | #log_path = ./logs/ 5 | 6 | # Logging levels: DISABLED, DEBUG, INFO, WARNING, ERROR, CRITICAL 7 | log_level = INFO 8 | 9 | # Log older than that will be deleted 10 | log_days = 30 -------------------------------------------------------------------------------- /examples/1_simple.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # just for the tests, to fetch the development version of canister in the parent directory 3 | sys.path.insert(0, '..') 4 | 5 | import bottle 6 | import canister 7 | from canister import session 8 | 9 | app = bottle.Bottle() 10 | app.install(canister.Canister()) 11 | 12 | 13 | @app.get('/') 14 | def index(): 15 | return 'It works!' 16 | 17 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /examples/views/upload.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # python setup.py sdist upload 2 | from setuptools import setup 3 | 4 | setup( 5 | name='canister', 6 | version='1.5.2', 7 | description='A bottle wrapper to provide logging, sessions and authentication.', 8 | url='https://github.com/dagnelies/canister', 9 | author='Arnaud Dagnelies', 10 | author_email='arnaud.dagnelies@gmail.com', 11 | license='MIT', 12 | keywords='bottle server webserver session logging authentication', 13 | py_modules=['canister'], 14 | scripts=['canister.py'], 15 | install_requires=[ 16 | 'bottle' 17 | ], 18 | extras_require={ 19 | 'jwt': ['PyJWT'] 20 | } 21 | ) -------------------------------------------------------------------------------- /examples/upload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ab -n 1000 -c 10 http://localhost:8080/hello/world 4 | # ab -n 2 -c 2 http://127.0.0.1:8080/hello/world 5 | 6 | import gevent.monkey 7 | gevent.monkey.patch_all() 8 | 9 | import sys 10 | sys.path.insert(0, '..') 11 | import canister 12 | import time 13 | import bottle 14 | import os.path 15 | 16 | can = canister.build('upload.config') 17 | 18 | @can.get('/') 19 | @bottle.view('upload.html') 20 | def index(): 21 | pass 22 | 23 | @can.route('/upload', method='POST') 24 | def do_upload(): 25 | upload = bottle.request.files.get('upload') 26 | upload.save('uploads/' + upload.filename) 27 | return 'OK' 28 | 29 | can.run() -------------------------------------------------------------------------------- /examples/4_params.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # just for the tests, to fetch the development version of canister in the parent directory 3 | sys.path.insert(0, '..') 4 | 5 | import bottle 6 | import canister 7 | from canister import session 8 | 9 | app = bottle.Bottle() 10 | app.install(canister.Canister()) 11 | 12 | 13 | @app.get('/') 14 | def index(foo=None, bar=None): 15 | return ''' 16 |17 | Function parameters are automatically extracted from the request if present. 18 | foo: %s 19 | bar: %s 20 |21 | /?foo=Foooooooo&bar=Baaaaaaaar 22 | ''' % (foo,bar) 23 | 24 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /examples/3_sessions.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # just for the tests, to fetch the development version of canister in the parent directory 3 | sys.path.insert(0, '..') 4 | 5 | import bottle 6 | import canister 7 | from canister import session 8 | 9 | app = bottle.Bottle() 10 | app.install(canister.Canister()) 11 | 12 | 13 | @app.get('/') 14 | def index(): 15 | if 'counter' in session.data: 16 | session.data['counter'] += 1 17 | else: 18 | session.data['counter'] = 0 19 | 20 | return '''
21 | Refresh the page to see the counter increase. 22 | Session sid: %s 23 | Session data: %s 24 |''' % (session.sid, session.data) 25 | 26 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /examples/7_oauth2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ab -n 1000 -c 10 http://localhost:8080/hello/world 4 | # ab -n 2 -c 2 http://127.0.0.1:8080/hello/world 5 | 6 | import sys 7 | sys.path.insert(0, '..') 8 | import canister 9 | import time 10 | import bottle 11 | from canister import session 12 | 13 | app = bottle.Bottle() 14 | app.config.load_config('7_oauth2.config') 15 | app.install(canister.Canister()) 16 | 17 | @app.get('/') 18 | def index(): 19 | return ''' 20 |
21 | Session sid: %s 22 | Session user: %s 23 |24 | Now try to send a brearer token in the Authorization header. 25 | ''' % (session.sid, session.user) 26 | 27 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine upload dist/* 27 | -------------------------------------------------------------------------------- /examples/8_gevent.py: -------------------------------------------------------------------------------- 1 | import sys 2 | # just for the tests, to fetch the development version of canister in the parent directory 3 | sys.path.insert(0, '..') 4 | 5 | import bottle 6 | import canister 7 | from canister import session 8 | 9 | app = bottle.Bottle() 10 | app.install(canister.Canister()) 11 | 12 | 13 | @app.get('/') 14 | def index(foo=None): 15 | if 'counter' in session.data: 16 | session.data['counter'] += 1 17 | else: 18 | session.data['counter'] = 0 19 | 20 | return ''' 21 |
22 | Session sid: %s 23 | Session user: %s 24 | Session data: %s 25 | "?foo=...": %s 26 |27 | ''' % (session.sid, session.user, session.data, foo) 28 | 29 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /examples/cgevent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ab -n 1000 -c 10 http://localhost:8080/hello/world 4 | # ab -n 2 -c 2 http://127.0.0.1:8080/hello/world 5 | 6 | import gevent.monkey 7 | gevent.monkey.patch_all() 8 | 9 | import sys 10 | sys.path.insert(0, '..') 11 | import canister 12 | import time 13 | import bottle 14 | 15 | can = canister.build('cgevent.config') 16 | 17 | @can.get('/') 18 | def index(): 19 | if can.session.user: 20 | return "Hi " + str(can.session.user) + "!"; 21 | else: 22 | err = bottle.HTTPError(401, "Login required") 23 | err.add_header('WWW-Authenticate', 'Basic realm="%s"' % 'private') 24 | return err 25 | 26 | @can.get('/hello/
23 | Without config, all logs go to the console. 24 | If "log_path=..." is set, they are written to a rotating log file. 25 | Note that even when the log is written, the underlying WSGI server may still write to stdout, stderr or elsewhere. 26 |''' 27 | 28 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /examples/basic_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ab -n 1000 -c 10 http://localhost:8080/hello/world 4 | # ab -n 2 -c 2 http://127.0.0.1:8080/hello/world 5 | 6 | import sys 7 | sys.path.insert(0, '..') 8 | import canister 9 | import time 10 | import bottle 11 | from canister import session 12 | 13 | app = bottle.Bottle() 14 | app.config.load_config('auth.config') 15 | app.install(canister.Canister()) 16 | 17 | @app.get('/') 18 | def index(): 19 | return ''' 20 |
21 | Session sid: %s 22 | Session user: %s 23 |24 | Basic Auth (username: alice, password: my-secret) 25 | ''' % (session.sid, session.user) 26 | 27 | 28 | @app.get('/secret') 29 | def secret(): 30 | if not session.user: 31 | err = bottle.HTTPError(401, "Login required") 32 | err.add_header('WWW-Authenticate', 'Basic realm="%s"' % 'private') 33 | return err 34 | 35 | return 'You are authenticated as ' + str(session.user) 36 | 37 | app.run(host='0.0.0.0') -------------------------------------------------------------------------------- /examples/5_custom_auth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # ab -n 1000 -c 10 http://localhost:8080/hello/world 4 | # ab -n 2 -c 2 http://127.0.0.1:8080/hello/world 5 | 6 | import sys 7 | sys.path.insert(0, '..') 8 | import canister 9 | import time 10 | import bottle 11 | from canister import session 12 | 13 | app = bottle.Bottle() 14 | app.install(canister.Canister()) 15 | 16 | @app.get('/') 17 | def index(): 18 | return ''' 19 |
20 | Session sid: %s 21 | Session user: %s 22 |23 |