Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Letters from the Battlefield

Letters from the Battlefield

Presentation I gave at PyCon Apac in Seoul 2016.

Avatar for Armin Ronacher

Armin Ronacher

August 14, 2016
Tweet

More Decks by Armin Ronacher

Other Decks in Programming

Transcript

  1. the thing about overengineering PREFACE overengineering |ˈōvərˌenjəˈniriNG| noun the designing

    of a product to be more robust or complicated than is necessary for its application
  2. being afraid of changes PROLOGUE afraid |əˈfrād| adjective worried that

    something undesirable will occur or be done: he was afraid that the farmer would send the dog after them
  3. changes • developers should never feel afraid of code changes

    • developers should not be afraid of the first change • developers should feel comfortable doing big changes • developers should not accidentally produce security problems
  4. bite size chunks • write code so that developers are

    never overwhelmed • neither on making new features • nor on changing existing code • simplifies code review
  5. where is the state? CHAPTER 1 state |stāt| noun the

    particular condition that someone or something is in at a specific time
  6. state in programming • Most prominent languages are rich in

    state • But poor in explicitly managing it • Most programmers do not know how their own state works • No rules when mutable state becomes assumed constant state
  7. why is that a problem? • Most prominent languages are

    rich in state • But poor in explicitly managing it • Most programmers do not know how their own state works
  8. practical example from functools import update_wrapper from django.conf import settings

    def might_debug(f): def new_func(*args, **kwargs):
 if settings.DEBUG: do_some_debug_stuff() return f(*args, **kwargs) return update_wrapper(new_func, f)
  9. is ‘settings’ mutable? • it's python, so the answer is

    yes • however at which point is it safe to modify them? • what if people drag out state to an unsafe scope?
  10. decision made from functools import update_wrapper from django.conf import settings

    if settings.DEBUG: def might_debug(f): def new_func(*args, **kwargs):
 do_some_debug_stuff() return f(*args, **kwargs) return update_wrapper(new_func, f) else: might_debug = lambda x: x
  11. module state in python • imports are stateful • module

    scope is stateful • this influences code we write in Python • modules in Python are giant singletons • the scope of state can be hidden
  12. decisions made from hidden state from django.utils.translation import ugettext class

    LoginForm(…): ERROR = ugettext(u"Could not sign in")
  13. decisions made from hidden state def handle_request(request): endpoint, args, kwargs

    = match_request(request) func = import_view_function(endpoint) return func(*args, **kwargs)
  14. shackle the state! CHAPTER 2 shackle |ˈSHak(ə)l| verb restrain; limit:

    they seek to shackle the oil and gas companies by imposing new controls.
  15. stateful APIs suck • nobody likes stateful APIs • in

    particular nobody likes APIs that randomly change behavior
  16. ideal state management • create scope • set up initial

    working conditions (modify here) • execute code • clean up state • destroy scope
  17. prevent access • If something is not there, say so,

    not not fall back • translations should not silently become idempotent calls
  18. raise if accessed in bad scope >>> from flask import

    request >>> request.headers Traceback (most recent call last): … RuntimeError: Working outside of request context.
  19. prevent stupid code >>> settings.transaction() Traceback (most recent call last):

    File "<stdin>", line 1, in <module> RuntimeError: Settings are closed. No more modifications
  20. import madness CHAPTER 3 madness |ˈmadnəs| noun the state of

    being mentally ill, especially severely.
  21. the art of importing • import all • upfront •

    do not import at runtime • there be many evil backstabbing dragons
  22. import all stuff from werkzeug.utils import find_modules def import_all(pkg): for

    module in find_modules(pkg, recursive=True): __import__(module) import_all(__name__.split('.')[0])
  23. why? • importing requires locks; imports can be recursive •

    imports have side effects, let's get it done early • both those things are bad • once it's imported, it's cached • after that things become much, much more predictable
  24. circular dependencies • good luck with that ;-) • I

    do not have a good response to this.
  25. make it searchable CHAPTER 4 search |sərCH| verb try to

    find something by looking or otherwise seeking carefully and thoroughly: I searched among the rocks, but there was nothing
  26. why? • new developers need to understand context • when

    you have conceptional security issues you need to find things • aids code review
  27. what's ‘searchable’ • assume your only tool is grep •

    write code so that you can grep/full text search it • it will be worth it
  28. things that are easily grep-able • decorators! • explicit and

    clear function and class names • special methods • avoid funky operator overloads if they do something non-standard
  29. predict common behavior CHAPTER 5 predict |prəˈdikt| verb say or

    estimate that (a specified thing) will happen in the future or will be a consequence of something: he predicts that the trend will continue
  30. my least favorite code import json from django.http import HttpResponse

    def view_function(request): some_data = generate_some_data(…) return HttpResponse(json.dumps(some_data), mimetype='application/json')
  31. what about this? from myproject.api import ApiResponse def view_function(): some_data

    = generate_some_data(…) return ApiResponse(some_data)
  32. why? • we establish “request context” • we define a

    clear common case of “this is the result of an API” • we can transform and handle data on the way out
  33. what do we gain? • JSON encode security issues? One

    clear point to handle it • Need to support a custom mimetype? Change all in one go • Instrumentation? One common object
  34. convert common values def handle_request(request): rv = dispatch_request(request) if isinstance(rv,

    ApiResponse): rv = Response(json.dumps(rv), mimetype='application/json', status=rv.status_code)
 return rv
  35. define context CHAPTER 6 context |ˈkäntekst| noun the circumstances that

    form the setting for an event, statement, or idea, and in terms of which it can be fully understood and assessed
  36. what is context • runtime context (“scopes”) • data context

    (“transfer encodings”) • security context (“who is the actor?”)
  37. context behavior • what happens based on context? • how

    does data look like? • how does context influence what is happening?
  38. examples of scoped context • current language • current http

    request • current authenticated user • current access restrictions
  39. implied context >>> from myapp.i18n import ugettext, set_language >>> with

    set_language("en_US"): ... ugettext("Sign in") ... u"Sign in" >>> with set_language("de_DE"): ... ugettext("Sign in") ... u"Anmelden"
  40. context for data • object in string context • object

    in HTML context • object serialization
  41. data in context >>> from markupsafe import Markup, escape >>>

    unicode(my_user) u"Peter Doe" >>> escape(my_user) u'<a href="/users/42/">Peter Doe</a>' >>> Markup("<em>%s</em>") % my_user u'<em><a href="/users/42/">Peter Doe</a></em>' >>> print json.dumps(my_user) {"username": "Peter Doe", "id": 42}
  42. prevent misuse CHAPTER 7 misuse |ˌmisˈyo͞os| noun the wrong or

    improper use of something: a misuse of power.
  43. context for improved security from myapp.db import Model, Query from

    myapp.access import get_available_organizations class Project(Model): … @property def query(self):
 org_query = get_available_organizations() return Query(self).filter( Project.organization.in_(org_query))
  44. automatic escaping • Template engines escape data automatically by HTML

    rules • However HTML is complex in behavior (script tags, attributes etc.) • It becomes possible to accidentally misuse things • People will get it wrong, so worth investigating the options
  45. JSON in HTML • Common case to send JSON to

    HTML • Two areas of concern: HTML attributes and <script> tags • How to escape in those. Common case? Can we make one function for both?
  46. example escaping >>> from flask.json import htmlsafe_dumps >>> print htmlsafe_dumps("<em>var

    x = 'foo';</em>") "\u003cem\u003evar x = \u0027foo\u0027;\u003c/em\u003e"
  47. result of this exercise • does not produce any HTML

    entities • now works in <script> … • … as well as single quoted attributes • falls over very obviously in double quoted attributes • it's pretty clear how it's supposed to work and hard to misuse