How do you build an API that developers love building and consumers love using?
There's a lot that goes into creating a great API. This presentation shares some tips & tricks, architectural patterns, and best practices that go into building a great engineering environment around your API.
Talk presented on Oct 18, 2017 at PloneConf2017.
Topics covered by this talk:
Intuitive Practices:
standardization, configuration/environment files, ORMs, SQLAlchemy, database migrations, Alembic, database seeds, requirements.txt, package management, dependency management, setup scripts
Durable Practices:
Unit Tests, virtual environments, flush vs commit, error rollbacks, request lifecycle, session lifecycle
Flexible Practices:
Directory structures, application factories, blueprints, python debugger
Reliable Practices:
Logging, progressive rollouts, slack hooks, cron health checks, api versioning, api analytics
Use Friendly Practices:
Endpoint design, endpoint documentation, debugging tools, postman
Speed Practices:
Python profiling, Bulk SQL Inserts, caching
14. Object Relational Mappers (3)
With ORM Without ORM
def get_user_email(id):
return conn.query(User.email)
.get(id)
def get_user_email(id):
query = text(”””
SELECT email FROM users
WHERE id = %d LIMIT 1;”””%(id))
rows = conn.execute(query)
if rows is not None:
return rows[0][’email’]
return None
^^ Much simpler
^^ Might have to worry about SQL Injection
21. Where Are My Packages?
$ python run.py
ImportError: No module named flask_sqlalchemy
$ pip install flask_sqlalchemy
$ python run.py
ImportError: No module named smtplib
$ pip install smtplib
$ python run.py
ImportError: No module named passlib
$ ... #FacePalm
24. Why aren’t my files updating?
$ find . -name '*.pyc' -delete
Your project may be running pre-compiled .pyc files and not overriding the old files
Common .pyc conflict scenarios:
• Moved or renamed python directories
• Moved or renamed python files
• Added or removed __init__.py files
• Created a variable with the same name as a module
To clear out all .pyc files and start fresh, run the following:
43. Flexible Testing (pdb)
Typical Python Debugging
def my_func(objects):
print "MY_FUNC", objects
if len(objects) > 0:
print "NOT EMPTY"
new_objects = some_func(objects)
<< ERROR >>
print new_objects
else:
print "EMTPY"
new_objects = []
return new_objects
• Lots of print statements
• Only renders prints run before the app crashes
• Can’t update values
Python Debugger
import pdb
def my_func(objects):
pdb.set_trace()
if len(objects) > 0:
new_objects = some_func(objects)
<< ERROR >>
else:
new_objects = []
return new_objects
• Sets a break-point at `.set_trace()`
• Functions like JS browser debugging break-points
• Can step line-by-line or continue until next break-point
• At each line, there is an interactive terminal
• Not only can print, but update values
55. Bulk SQL Inserts
Three useful methods
• bulk_save_objects
• bulk_insert_mappings
• Mogrify
ORMs were not primarily
intended for bulk usage
Using bulk commands
gives a 5-10x speed boost
# Without bulk
for i in range(100000):
conn.add(Employee(name="EMP #" + str(i)))
conn.commit()
# With bulk
conn.bulk_save_objects([
Employee(name="EMP #" + str(i))
for i in range(100000)
])