├── .gitignore ├── README.md ├── docs ├── img │ └── nope.gif ├── index.html ├── monokai-sublime.css ├── part1-intro.md ├── part2-wsgi.md ├── part3-request-response.md ├── part4-routing.md ├── part5-controllers.md ├── part6-data.md ├── part7-closing.md ├── reveal.js └── reveal.js-3.5.0 │ ├── .gitignore │ ├── .travis.yml │ ├── CONTRIBUTING.md │ ├── Gruntfile.js │ ├── LICENSE │ ├── README.md │ ├── bower.json │ ├── css │ ├── print │ │ ├── paper.css │ │ └── pdf.css │ ├── reveal.css │ ├── reveal.scss │ └── theme │ │ ├── README.md │ │ ├── beige.css │ │ ├── black.css │ │ ├── blood.css │ │ ├── league.css │ │ ├── moon.css │ │ ├── night.css │ │ ├── serif.css │ │ ├── simple.css │ │ ├── sky.css │ │ ├── solarized.css │ │ ├── source │ │ ├── beige.scss │ │ ├── black.scss │ │ ├── blood.scss │ │ ├── league.scss │ │ ├── moon.scss │ │ ├── night.scss │ │ ├── serif.scss │ │ ├── simple.scss │ │ ├── sky.scss │ │ ├── solarized.scss │ │ └── white.scss │ │ ├── template │ │ ├── mixins.scss │ │ ├── settings.scss │ │ └── theme.scss │ │ └── white.css │ ├── demo.html │ ├── index.html │ ├── js │ └── reveal.js │ ├── lib │ ├── css │ │ └── zenburn.css │ ├── font │ │ ├── league-gothic │ │ │ ├── LICENSE │ │ │ ├── league-gothic.css │ │ │ ├── league-gothic.eot │ │ │ ├── league-gothic.ttf │ │ │ └── league-gothic.woff │ │ └── source-sans-pro │ │ │ ├── LICENSE │ │ │ ├── source-sans-pro-italic.eot │ │ │ ├── source-sans-pro-italic.ttf │ │ │ ├── source-sans-pro-italic.woff │ │ │ ├── source-sans-pro-regular.eot │ │ │ ├── source-sans-pro-regular.ttf │ │ │ ├── source-sans-pro-regular.woff │ │ │ ├── source-sans-pro-semibold.eot │ │ │ ├── source-sans-pro-semibold.ttf │ │ │ ├── source-sans-pro-semibold.woff │ │ │ ├── source-sans-pro-semibolditalic.eot │ │ │ ├── source-sans-pro-semibolditalic.ttf │ │ │ ├── source-sans-pro-semibolditalic.woff │ │ │ └── source-sans-pro.css │ └── js │ │ ├── classList.js │ │ ├── head.min.js │ │ └── html5shiv.js │ ├── package.json │ ├── plugin │ ├── highlight │ │ └── highlight.js │ ├── markdown │ │ ├── example.html │ │ ├── example.md │ │ ├── markdown.js │ │ └── marked.js │ ├── math │ │ └── math.js │ ├── multiplex │ │ ├── client.js │ │ ├── index.js │ │ ├── master.js │ │ └── package.json │ ├── notes-server │ │ ├── client.js │ │ ├── index.js │ │ └── notes.html │ ├── notes │ │ ├── notes.html │ │ └── notes.js │ ├── print-pdf │ │ └── print-pdf.js │ ├── search │ │ └── search.js │ └── zoom-js │ │ └── zoom.js │ └── test │ ├── examples │ ├── assets │ │ ├── image1.png │ │ └── image2.png │ ├── barebones.html │ ├── embedded-media.html │ ├── math.html │ ├── slide-backgrounds.html │ └── slide-transitions.html │ ├── qunit-1.12.0.css │ ├── qunit-1.12.0.js │ ├── simple.md │ ├── test-markdown-element-attributes.html │ ├── test-markdown-element-attributes.js │ ├── test-markdown-external.html │ ├── test-markdown-external.js │ ├── test-markdown-options.html │ ├── test-markdown-options.js │ ├── test-markdown-slide-attributes.html │ ├── test-markdown-slide-attributes.js │ ├── test-markdown.html │ ├── test-markdown.js │ ├── test-pdf.html │ ├── test-pdf.js │ ├── test.html │ └── test.js ├── ex1-1 └── it_works.py ├── ex2-2 └── simple_wsgi_app.py ├── ex3-1 ├── simple_http_objects.py └── werkzeug_http_objects.py ├── ex3-2 ├── bizkit.py └── hello_bizkit.py ├── ex4-1 ├── bizkit.py └── hello_goodbye.py ├── ex5-1 ├── bizkit.py ├── greeting.html └── hello_goodbye_templates.py ├── ex6-1 ├── bizkit.py ├── greeting.html ├── greeting_persist.py └── greetings.sqlite └── tasks.py /.gitignore: -------------------------------------------------------------------------------- 1 | .python-version 2 | __pycache__ 3 | twistd.* 4 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's build a web framework! 2 | 3 | Slides and exercises from my PyCon US 2017 tutorial. 4 | 5 | [View the slides here](https://jacobian.github.io/pycon2017/) 6 | -------------------------------------------------------------------------------- /docs/img/nope.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobian/pycon2017/8f80276738eb7bc809cc0555d8311877c27d1dd8/docs/img/nope.gif -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 18 | 19 | 20 |I've said "$greeting" to $name $count times.
10 | 11 | -------------------------------------------------------------------------------- /ex6-1/greeting_persist.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from contextlib import closing 3 | from bizkit import TemplateResponse, Router 4 | 5 | class GreetingDatabase: 6 | def __init__(self): 7 | self.conn = sqlite3.connect('greetings.sqlite') 8 | self.conn.execute('''CREATE TABLE IF NOT EXISTS greeting_counts 9 | (greeting text, name text, greeting_count integer)''') 10 | 11 | def get_and_increment_count(self, greeting, name): 12 | # RACE CONDITIONS AHOY! 13 | with closing(self.conn.cursor()) as c: 14 | c.execute( 15 | "SELECT greeting_count FROM greeting_counts WHERE greeting=? AND name=?", 16 | [greeting, name] 17 | ) 18 | rows = c.fetchall() 19 | 20 | if rows: 21 | count = rows[0][0] + 1 22 | c.execute( 23 | "UPDATE greeting_counts SET greeting_count=? WHERE greeting=? AND name=?", 24 | [count, greeting, name] 25 | ) 26 | else: 27 | count = 1 28 | c.execute( 29 | "INSERT INTO greeting_counts (greeting, name, greeting_count) VALUES (?, ?, 1)", 30 | [greeting, name] 31 | ) 32 | 33 | self.conn.commit() 34 | return count 35 | 36 | def hello(request, name): 37 | db = GreetingDatabase() 38 | count = db.get_and_increment_count("hello", name) 39 | context = {"greeting": "Hello", "name": name, "count": count} 40 | return TemplateResponse("greeting.html", context) 41 | 42 | def goodbye(request, name): 43 | db = GreetingDatabase() 44 | count = db.get_and_increment_count("goodbye", name) 45 | context = {"greeting": "Goodbye", "name": name, "count": count} 46 | return TemplateResponse("greeting.html", context) 47 | 48 | routes = Router() 49 | routes.add_route(r'/hello/(.*)/$', hello) 50 | routes.add_route(r'/goodbye/(.*)/$', goodbye) 51 | -------------------------------------------------------------------------------- /ex6-1/greetings.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jacobian/pycon2017/8f80276738eb7bc809cc0555d8311877c27d1dd8/ex6-1/greetings.sqlite -------------------------------------------------------------------------------- /tasks.py: -------------------------------------------------------------------------------- 1 | import os 2 | import signal 3 | from invoke import run, task, Collection 4 | 5 | @task 6 | def start_slides(ctx): 7 | if not os.path.exists("twistd.pid"): 8 | ctx.run("twistd web --port tcp:5000 --path docs") 9 | 10 | @task 11 | def stop_slides(ctx): 12 | if os.path.exists("twistd.pid"): 13 | pid = int(open("twistd.pid").read().strip()) 14 | os.kill(pid, signal.SIGTERM) 15 | 16 | RUNNERS = { 17 | # example: (WSGI app, additional environ) 18 | 'ex1-1': ('it_works.demo_app', {}), 19 | 'ex2-2': ('simple_wsgi_app.application', {}), 20 | 'ex3-1': ('simple_http_objects.application', {}), 21 | 'ex3-2': ('bizkit.application', {'BIZKIT_APP': 'hello_bizkit:hello'}), 22 | 'ex4-1': ('bizkit.application', {'BIZKIT_APP': 'hello_goodbye'}), 23 | 'ex5-1': ('bizkit.application', {'BIZKIT_APP': 'hello_goodbye_templates'}), 24 | 'ex6-1': ('bizkit.application', {'BIZKIT_APP': 'greeting_persist'}), 25 | } 26 | 27 | @task(name="run") 28 | def run_example(ctx, ex): 29 | if not ex.startswith('ex'): 30 | ex = 'ex' + ex 31 | if ex not in RUNNERS: 32 | print(f"No such example: {ex}") 33 | return 34 | 35 | os.chdir(ex) 36 | wsgi_app, env = RUNNERS[ex] 37 | env['PYTHONPATH'] = '.' 38 | ctx.run(f'twist --log-format text web --wsgi {wsgi_app} --port tcp:8080', env=env, echo=True) 39 | 40 | slides = Collection("slides") 41 | slides.add_task(start_slides, "start") 42 | slides.add_task(stop_slides, "stop") 43 | 44 | ns = Collection() 45 | ns.add_collection(slides) 46 | ns.add_task(run_example) 47 | --------------------------------------------------------------------------------