Thursday, October 5, 2017
Use Your iPad as a Second Monitor
Thursday, August 17, 2017
Programmatically Logging in to a site with the Auth0 Lock Widget
In a nutshell, there are 4 calls that have to be made for a successful login
- GET to the site you are trying to log in to (to get a state variable)
- POST to Auth0 with the username and password
- POST to the callback handler on Auth0 with the results from the previous POST
- GET to the redirect page that the previous POST indicates to redirect to
Let's hope that Auth0 doesn't change it's login specs anytime soon!
Thursday, January 26, 2017
Nested send and hash/array access for Ruby Objects
value = obj.send_nested("data.foo['bar'].id")
and under the hood this will do something akin to
obj.send(data).send(foo)['bar'].send(id)
This also works with symbols in the attribute string
value = obj.send_nested('data.foo[:bar][0].id')
which will do something akin to
obj.send(data).send(foo)[:bar][0].send(id)
In the event that you want to use indifferent access you can add that as a parameter as well. E.g.
value = obj.send_nested('data.foo[:bar][0].id', with_indifferent_access: true)
Since it's a bit more involved, here is the link to the gist that you can use to add that method to the base Ruby Object. The gist also includes the relevant tests for your peace of mind.
Monday, November 7, 2016
Converting an ADT HL7 message to JSON
Although the HL7 standard makes it less verbose when sending the information along, I prefer working with JSON objects as opposed to pipe delimited strings. If you are in the same boat, then you can use the following two python functions to convert an HL7 message to an easier to digest/understand. (Note these two functsion depend on the hl7apy python library)
Yes, these functions return a python dictionary and not a JSON object, but you can trivially convert a dictionary to a JSON string.
import json
from hl7apy.parser import parse_message
# Taken from http://hl7apy.org/tutorial/index.html#elements-manipulation
s = """MSH|^~\&|GHH_ADT||||20080115153000||ADT^A01^ADT_A01|0123456789|P|2.5||||AL
EVN||20080115153000||AAA|AAA|20080114003000
PID|1||566-554-3423^^^GHH^MR||EVERYMAN^ADAM^A|||M|||2222 HOME STREET^^ANN ARBOR^MI^^USA||555-555-2004~444-333-222|||M
NK1|1|NUCLEAR^NELDA^W|SPO|2222 HOME STREET^^ANN ARBOR^MI^^USA"""
# Convert it
d = hl7_str_to_dict(s)
# Dump it as a JSON string
print json.dumps(d)
Hope this helps someone who appreciates new data representations more than old data representations ;)
Thursday, September 22, 2016
Simple open_sftp() context manager for sftp read and writing of files
open() for local files. Usage is as simple as
from open_sftp import open_sftp
path = "sftp://user:p@ssw0rd@test.com/path/to/file.txt"
# Read a file
with open_sftp(path) as f:
s = f.read()
print s
# Write to a file
with open_sftp(path, mode='w') as f:
f.write("Some content.")
It's as simple as that. The full code can be found as a gist on GitHub.
Note: This assumes that the directory already exists, but one could modify this trivially to create the path automatically by adding the details from this StackOverflow thread.
Wednesday, August 17, 2016
Configuring the Python Elasticsearch Client to use TLSv1.1
Basic Setup
First, here was snippet of code that we were using to connect to our Elasticsearch instance. (Note,obviously that IP address isn't the one we are actually using)from elasticsearch import ElasticSearch
es = ElasticSearch(
"hosts": [
{
"host": "123.45.67.890",
"use_ssl": true
}
]
)
print es.info()
If you were to look at the docs, you'd thing that this is all that you would have to do. Unfortunately, this (most likely) won't work. And if you are reading this, then it probably didn't work for you either.
Symptom & Diagnosis
Running the above snippet yielded the following error message:ConnectionError: ConnectionError(HTTPSConnectionPool(host=u'123.45.67.890', port=9200):
Max retries exceeded with url: / (Caused by : ))
caused by: MaxRetryError(HTTPSConnectionPool(host=u'123.45.67.890', port=9200):
Max retries exceeded with url: / (Caused by : ))
We then checked in a regular browser to make sure that we can actually reach the Elasticsearch server (i.e. visited https://123.45.67.890:9200) and we indeed were able to connect and we received a nice response with some basic config details.
Following this we did a tcpdump to make sure that we actually were able to connect the the Elasticsearch server, and, as you might expect, according to the dump, a TCP connection was being made. More specifically, we did:
sudo tcpdump -n host 123.45.67.890
With a result that included valid connections and responses from the server:
...
16:50:49.060077 IP 10.1.248.172.49322 > 123.45.67.890.9200: Flags [S], seq 4274669687,
win 65535, options [mss 1366,nop,wscale 5,nop,nop,TS val 1362130305 ecr 0,
sackOK,eol], length 0
16:50:49.125589 IP 10.1.248.172.49322 > 123.45.67.890.9200: Flags [.], ack 1,
win 8192, length 0
16:50:49.127457 IP 10.1.248.172.49322 > 123.45.67.890.9200: Flags [P.], seq 1:96,
ack 1, win 8192, length 95
...
So, by the looks of it, we were able to connect to the server with a browser, AND our python snippet was correctly sending data to our server, but things were not working. After some head scratching we looked at the logs on the Elasticsearch server (in our case that was in /var/log/messages)and discovered the following interesting error:
javax.net.ssl.SSLHandshakeException: Client requested protocol TLSv1
not enabled or not supported
at sun.security.ssl.Handshaker.checkThrown(Handshaker.java:1431)
at sun.security.ssl.SSLEngineImpl.checkTaskThrown(SSLEngineImpl.java:535)
at sun.security.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:813)
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:781)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:624)
at org.jboss.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1218)
at org.jboss.netty.handler.ssl.SslHandler.decode(SslHandler.java:852)
at org.jboss.netty.handler.codec.frame.FrameDecoder.callDecode(
FrameDecoder.java:425)
at org.jboss.netty.handler.codec.frame.FrameDecoder.messageReceived(FrameDecoder.java:303)
at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(
SimpleChannelUpstreamHandler.java:70)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(
DefaultChannelPipeline.java:564)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(
DefaultChannelPipeline.java:559)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:88)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(
AbstractNioWorker.java:108)
at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(
AbstractNioSelector.java:337)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:89)
at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:178)
at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
It looks like by default the Elasticsearch client uses TLSv1. Now, most machines (correctly) have TLSv1 disabled due to known vulnerabilities. But don't worry, before getting upset about having to downgrade to an insecure TLSv1, there is a very easy solution to this problem.
Solution
The only thing that you have to change when you setup the client it to make it use theRequestsHttpConnection. It's really as simple as that.
from elasticSearch import ElasticSearch, RequestsHttpConnection
es = ElasticSearch(
"hosts": [
{
"host": "123.45.67.890",
"use_ssl": true
}
],
connection_class=RequestsHttpConnection
)
print es.info()
Note this will require you to install the requests library.
In the documentation this functionality is described when using it to connect to AWS with IAM, but not as how one should set it up to use TLSv1.1. Well, I guess now we know.
Hopefully this saves someone some pain and frustration!
Monday, July 25, 2016
Start of day in UTC timezone
from datetime import datetime, date, time, timedelta
utc_offset = datetime.utcnow() - datetime.now()
today_start = datetime.combine(date.today(), time())
today_start += utc_offset
today_end = today_start + timedelta(hours=24)
Hopefully this saves someone a little time.
Thursday, January 14, 2016
Web Application Data Input Validation and Easy Documenting (Flask) Routes in one Fell Swoop
- Input validation for HTTP parameters and JSON data
- API Documentation (that doesn't go stale)
First, let's see two examples of things that are commonly done: validating HTTP parameters and validating JSON data
@route('/foo')
def foo():
try:
param1 = request.args['param1']
except:
return "param1 missing", 400
try:
param2 = int(request.args.get('param2'))
except:
return "param2 needs to be an int", 400
# Do stuff
@route('/bar')
def bar():
try:
param1 = request.json['param1']
except:
return "param1 missing", 400
try:
param2 = int(request.json.get('param2'))
except:
return "param2 needs to be an int", 400
# Do stuff
So, when one looks at those functions, the only way to know that param1 is required and param2 needs to be an integer is to actually look at the code. Not only is ugly and hard to maintain, it is also hard to understand.One potential way to validate input JSON is to use JSON Schema. (There is even a flask-jsonschema project for exactly this). The pitfall with this is that it depends on the JSON Schema standard that has a couple of limitations (e.g. hard to do validation across attributes). Instead I turned to the object serialization library marshmallow that has fantastic object validation methods. So, continuing with the above examples, we can create a marshmallow schema to define the valid input parameters.
MySchema(Schema):
param1 = fields.Str(required=True)
param2 = fields.Int()
Now that we have a schema to help us validate the input, we need a way to actually apply it to the input. We can accomplish this by using a decorator that applies the given schema to the input parameters and/or the JSON data -- I happened to call this decorator ensure. The function is a bit long so I won't include it here, but it is available in full here. Since both routes happen to have the same validation needs, we simply need to say where to apply the schema.
@route('/foo')
@ensure(params=MySchema)
def foo():
pass
@route('/bar')
@ensure(input=MySchema)
def bar():
pass
While defining explicit marshmallow schemas in advance is great if the same schema is reused (as in the above example), it is a painstaking process when having to generate explicit schemas for routes that are all different. As such, with a little bit of coding, we can create Schemas on the fly after being defined as a dictionary.def build_schema_from_dict(d, allow_nested=True):
"""Build a Marshmallow schema based on a dictionary of parameters
:param d: The dict of parameters to use to build the Schema
:param allow_nested: Whether or not nested schemas are allowed. If
``True`` then a fields.Nested() will be created
when there is a nested value.
:return: A Marshmallow schema based on the dictionary
"""
for k, v in d.iteritems():
if isinstance(v, tuple):
schema = v[0]
if len(v) > 1:
opts = v[1]
elif isinstance(v, dict):
schema = v
opts = {}
else:
continue
if not allow_nested:
raise ValueError("Nested attributes not allowed.")
# Recursively generate the nested schema(s)
schema = build_schema_from_dict(schema)
# Update the current dict with the Nested schema
d[k] = fields.Nested(schema, **opts)
return type('Schema', (Schema, ), d)
Combining the above function build_schema_from_dict and our ensure decorator, we can achieve the following@route('/foo')
@ensure(
params={
'param1': fields.Str(required=True),
'param2': fields.Int()
})
def foo():
# Do stuff. Oh, and request.params is a dict that has all of the
# validated values from the request. yay!
pass
@route('/bar')
@ensure(
input={
'param1': fields.Str(required=True),
'param2': fields.Int()
})
def bar():
# Do stuff. Oh, and request.input is a dict that has all of the
# validated values from the request. yay!
pass
Hurray! Now we have an easy to use way to both validate and document input by just decorating routes with the ensure decorator. Win, win!If you look at the details of the
ensure function available in full here, you will see that I add the validated data to the request object, so that it is easy to get the correctly typed data in the functionHappy coding!
Tuesday, December 15, 2015
Can your coworkers work in healthcare?
- The US Treasury's Specially Designated Nationals (SDN) List
- The Office of the Inspector General (OIG) Exclusion List
- The System for Award Management (SAM) Exclusion List
- FDA Clinical Investigations Disqualification Proceedings
- FDA Debarment List (Drug Product Applications)
- US Military TRICARE Sanction List
As such, I put together a simple python script that checks a list of people against all three lists by downloading the actual exclusion lists. The lists that it downloads are:
- SDN: https://www.treasury.gov/ofac/downloads/sdnlist.txt
- OIG: http://oig.hhs.gov/exclusions/downloadables/updatedleie.txt
- SAM: https://www.sam.gov/public-extracts/SAM-Public/SAM_Exclusions_Public_Extract.ZIP
- FDA: http://www.accessdata.fda.gov/scripts/SDA/sdExportData.cfm?sd=clinicalinvestigatorsdisqualificationproceedings&exportType=csv
It's nothing fancy and doesn't cover all cases, but it should be enough to get you started. Note, you will still have to perform actual background checks as well, but this can be run more frequently. You can run the script via:
python background_exclusion_check.pyNote, you will need to have requests installed as it uses that to download the files. In case you don't feel like going over to github to take a look at the code, here it is in full.
Monday, December 14, 2015
Python Wrapper around Zoom.us REST API
The library is on Pypi and can easily be installed via pip:
pip install zoomus
As you would expect, this wrapper just makes some of the usual things you do with the Zoom API a little easier. For example, this is how you would list all of the meetings that the users in your account have.
from zoomus import ZoomClient
client = ZoomClient('API_KEY', 'API_SECRET')
for user in client.user.list():
user_id = user['id']
print client.meeting.list('host_id': user_id)
As it currently stands this library is still in beta, but we will be adding a couple enhancements in the not too distant future (e.g. better error handling). Of course, you are always welcome to contribute to the github project. Hopefully this will be useful to you as well!
Wednesday, February 18, 2015
Need to convert your large Excel file to MediaWiki format?
Friday, December 5, 2014
The Four Steps to Attaining Test Writing Nirvana
Anyone that has started up a project will know that the biggest hurdle to testing is writing tests. That may sound silly, but writing tests takes time, effort, and practice. Furthermore, until there is a good testing infrastructure in place, writing tests can be painstakingly slow. As such, the tests that are written at the beginning of a project are usually the most brittle, cumbersome to maintain, and difficult to understand.
The problem is that regardless of project scope and domain and irrespective to testing framework or language that you use, testing of software goes through the following phases: manually created objects, fixtures and helper methods, factories, and finally scenarios.
Can one skip steps and head straight to Nirvana? Well, that depends on how much time you are willing to invest when you first start your project. Unfortunately, most of us will want to churn out "real" features as quickly as possible when we set out on our new adventure. Only once something is up and running do we then go back and flush out the details of testing. Of course, by the time you have your first set of features complete, you (and your collaborators) probably don't want to perform a feature freeze to fix your testing environment. Instead, you will keep test writing as a secondary concern and gradually migrate new tests to better ways of writing tests.
Is this bad? Not necessarily. As you embark on a project, the requirements and priorities of development shift. As a budding startup project, you will want to ship something as quickly as possible. This means cranking out as many gizmos, wizbangs, and whositswhatsits as possible. However, as you mature, you will want to be provide a more robust offering to your users. By the time your project has netted you enough money to buy a Ferrari (or a beer if it's open source), you'll want to make sure that everything is thoroughly tested. With respect to test writing, the aim of a software development team should be to get through these phases as quickly as possible (regardless of what those non-techies tell you!).
If you look at a graph of time spent on writing/maintaining tests and maturity of a project, I suspect it will look something like the bell curve. At first, minimal time is spent on testing - you have to push out those features!. Then, the first set of real bugs hit and you start investing in better testing (both in frameworks, testing standards, patterns, infrastructure, etc.). By the end, writing tests will be so easy that, even though you are writing more tests with better coverage, it will take less time to write and maintain them.
Steps to Nirvana
Although these principles apply to any language/framework/etc., I'm going to illustrate the phases with some simple python code that tests the correctness of a function that returns True if two users share a team. The same type of setup can be used to test a REST API or site written with Ruby on Rails, Node.js, PHP, etc.def is_on_same_team(user1, user2):
""""Return True if and only if the two users share a team."""
# Some implementation for this function. Details aren't important.
Note: The python code here is not the most efficient, or always the most pythonic, but was written with clarity in mind for those who haven't used python before. I'm also going to assume that we have some global variables (like db) that we can use to put things into some database.Phase 1: Manually creating objects
At first, we do what is simplest: we create the models directly and insert them into the database manually. We've all been there. We've all done it. There is no shame in it... well, not too much.def test_is_on_same_team():
user1 = User(
first_name="Sheldon"
last_name="Cooper"
)
db.put(user1)
user2 = User(
first_name="Leonard"
last_name="Hofstadter"
)
db.put(user2)
user3 = User(
first_name="Tony"
last_name="Stark"
)
db.put(user3)
team = Team(name="Big Bang Theory Cast")
db.put(team)
team_member1 = TeamMember(team_id=team.id, user_id=user1.id)
team_member2 = TeamMember(team_id=team.id, user_id=user2.id)
db.put(team_member1)
db.put(team_member2)
assertTrue(is_on_same_team(user1, user2)
assertFalse(is_on_same_team(user1, user3)
Obviously we do this because it's simple, but why is this so bad? There are many reasons why this is a poorly written test, but here are the main points:
- We had to write about 20 lines of code just to setup our test. This is both a waste of time, and it's hard for someone to glance at the test and quickly figure out what is going on.
- As the models required some values for things like first_name and last_name, we are forced to pick "random" values that have no bearing on the outcome of the test. In other words, if I'm not testing something dealing with a user's name, I shouldn't have to provide it.
- What happens if later we decide the middle_name is also a required parameter? Once that happens, we'll have to go through every test that creates a
Userobject and add in a randommiddle_name. No fun. Trust me. - We have to manually create the team association with the
TeamMemberobject. What happens if in the future we decide that there first needs to be an invitation step that requires some other models to be created? Again, we'll have to find all tests and update them. - Each of the models is manually inserted into the database. (In this example, I am assuming that there is some magic
db.put()method that does this for us for any object type). Since we are going to want to add it to some database 95% of the time, why should I have to explicitly write this line of code for each object that is created? Furthermore, if there are foreign key constraints, you have to make sure that you insert the objects in the right order. - We are testing both the positive case (
assertTrueandassertFalse) in one test case. For a real test suite, this should be split in to multiple tests, but we're going to leave it like this for the sake of motivating these examples.
Phase 2: Helper Functions & Fixtures
The firs time that you have to refactor your test(s) because one of your models changes, you will quickly create some helper functions that will hide the details of the model creation. For example, here we now assume that we have created three helper methods that help us create users, teams, and team-user associations. This is definitely a step up from before as we have addressed points 2 and 3, but there is still much to be desired.def test_is_on_same_team():
user1 = create_user()
user2 = create_user()
user3 = create_user()
db.put(user1)
db.put(user2)
db.put(user3)
team = create_team()
db.put(team)
team_member1 = create_team_membership(team, user1)
team_member2 = create_team_membership(team, user2)
db.put(team_member1)
db.put(team_member2)
assertTrue(is_on_same_team(user1, user2)
assertFalse(is_on_same_team(user1, user3)
Phase 3: Factories
After having written many helper functions to create simple objects, you'll undoubtedly move on to the next phase where you realize that in most cases there are many dependent objects that usually need to be created. For example, it could be the case that a user always has a profile image (e.g. you need one to sign up for the service). However, since in most of your tests you didn't need the profile image, you never created the model for it. This means, when you do need it (e.g. if you want to do something likeuser.profile_image.size() you first need to use a helper function to create an image object, and then associate it with the user. Although you can put this kind of logic in your helper functions, many people move on to using object factories (take a look at factorygirl for Ruby and factoryboy for python).Although this addresses the 5th issue noted above, on the surface we're not much better than in Phase 2 as we still have to specify exactly how the objects should be created (via the use of UserFactory, TeamFactory, etc). On the up-side, we have decoupled the generation of objects for testing with the usage of them in tests. The factories do all of the work of creating complete profiles, teams, etc. Assuming each profile needed a profile image, the
UserFactory should also create a ProfileImage object and associate that with the respective user. Furthermore, the use of factories sets us up for the next set of improvements that we can make. And look, we've already shrunk down our test setup code to 6 lines!def test_is_on_same_team():
user1 = UserFactory(db).create()
user2 = UserFactory(db).create()
user3 = UserFactory(db).create()
team = TeamFactory(db).create()
TeamMembershipFactory(db).create(team_id=team.id, user_id=user1.id)
TeamMembershipFactory(db).create(team_id=team.id, user_id=user2.id)
assertTrue(is_on_same_team(user1, user2)
assertFalse(is_on_same_team(user1, user3)
Phase 3.1: Customized Factories
The problem with the above code is that we still have to explicitly create thoseTeamMembership objects. This logic of adding someone to a team, shouldn't be part of a test as it's not integral to what we are testing. So, the natural thing to do is to pass this sort of data to the factory that creates the team. For example, below we have modified the TeamFactory to take a members parameter that will automatically create the associates for us. If the way that we create associations between users and teams ever changes, we only ever have to update the factory. Oh, and it also saves us several lines of code in each test that creates teams.At this point, we have solved pretty much all of the issues that were raised in Phase 1. The setup code has been reduced to 4 lines of code and all of the complexities of object generation, team associations, etc. have been moved to the factories. You may think that we're done on our path to enlightenment, but we still have a bit farther to go!
def test_is_on_same_team():
user1 = UserFactory(db).create()
user2 = UserFactory(db).create()
user3 = UserFactory(db).create()
team = TeamFactory(db).create(members=[user1, user2])
assertTrue(is_on_same_team(user1, user2)
assertFalse(is_on_same_team(user1, user3)
Phase 3.2: Factory Factory
You will soon realize that having to create and initialize each of the individual factories each time you want to use them (as we did above) is a waste of time and effort. As such, the next step is to create an object that contains all of the initialized factories. For example, a very simplistic way to achieve this is as following:class Factory(object):
def __init__(self, db):
self.user = UserFactory(db)
self.team = TeamFactory(db)
Although for the sake of clarity in the example below here we create a Factory object in the actual test, a better way to do this is to create this object in the set-up phase of your testing framework (E.g. unittest or nose in python, RSpec in Ruby). That way, you create the factories once when the testing framework starts up, and you can just use them in all of your testsdef test_is_on_same_team():
factories = Factory(db)
user1 = factories.user.create()
user2 = factories.user.create()
user3 = factories.user.create()
team = factories.team.create(members=[user1, user2])
assertTrue(is_on_same_team(user1, user2)
assertFalse(is_on_same_team(user1, user3)
It is interesting to note, that at this point we have decoupled the setup and testing even more. Not only are we delegating the creation of objects to a factory, but we are delegating the creation of the factories as well. This way, not only is it easy to change the creation of a specific object (by updating the factory), but it is easy to make sweeping changes to how the factories are instantiated (by changing the factory-factory).Phase 4: Scenarios
After having decoupled your factory creation to a factory-factory, you will soon realize that even calling the factories explicitly isn't clear or easy enough. In other words, you are still working with how to do something rather than the intent of what you want. As such, we move on to abstracting the setup of a test even further via the use of what I like to call "scenarios". The essence of a scenario is to just create a description of what you expect to be in the database for the use of your test and let some underlying magic make it happen.def test_is_on_same_team():
d = scenario({
'users': ['user1', 'user2', 'user3']
'teams': [
['user1', 'user2']
]
})
assertTrue(is_on_same_team(d['user1'], d['user2'])
assertFalse(is_on_same_team(d['user1'], d['user3'])
What you see above is a first pass at setting up such a scenario. We simply pass in the set of users and teams that we want created, and let the scenario generation code take care of calling the appropriate factories to setup the data for us (hence the variable name d). At this point, we have completely decoupled how we generate data for our tests from the actual writing of the test. Not only is the test now easier to read but it's easier to understand. This is because we have broken the test into two parts -- the setup phase where we describe what we intend to use to perform the test, and the actual test of the function. The above test as moved from a prescriptive setup to a descriptive setup.I won't got into the details, but here is a potential (very simplistic) implementation of the scenario function.
def scenario(definition):
factories = Factory(db)
users = {}
for key in definition['users']:
users.put(key, factories['user'].create())
teams = {}
for key in definition['teams']:
teams.put(key, factories['team'].create(members=t['members']))
return {
'users': users
'teams': teams
}
Although I show how to do this by returning a dictionary (aka a hashtable for your non-python folk), this can also be done by returning an actual object letting you get at the values with getters/setters instead of looking it up by key in the dictionary. For example, I find it much cleaner to have code that reads:assertTrue(is_on_same_team(d.user1, d.user2)
assertFalse(is_on_same_team(d.user1, d.user3)
Phase 4.1: Scenarios DSL
On our path to purity, the next improvement is to remove all of the unnecessary "code-like" attributes of the scenario setup. By using a Domain Specific Language (DSL) we can setup a scenario in plain-text with the same result. For instance, one can now imagine using something like the following:def test_is_on_same_team():
d = scenario("""
Users: user1, user2, user3
Teams:
user1, user2
""")
assertTrue(is_on_same_team(d.user1, d.user2)
assertFalse(is_on_same_team(d.user1, d.user3)
By using such a DSL it becomes very clear, even to someone that knows very little programming and/or next to nothing about the underlying system that you have developed, what this test is doing. That is the mark of a good test. Furthermore, since all of the details of setting up the data is relegated to the scenario function, any changes to the underlying system only need to be changed in a relatively few places.Phase 4.2: Scenario Names
If you find that you have a common set of scenarios that you always use, you can even predefine them in some other file for reuse. For example, it would not be hard to assume that we have tests dealing with teams and users, the scenario that we have is a quite common setup. So, let's say we setup a dictionary with all of these predefined scenarios up as follows:SCENARIOS = {
'three users with user1 and user2 sharing a team': """
Users: user1, user2, user3
Teams:
user1, user2
"""
}
Assuming we also modify our scenario function to check for predefined scenarios, we can now update our test todef test_is_on_same_team():
d = scenario('three users with user1 and user2 sharing a team')
assertTrue(is_on_same_team(d.user1, d.user2)
assertFalse(is_on_same_team(d.user1, d.user3)
Phase 4.3: Scenario Decorators
This part is a bit specific to python, but I presume something similar can be achieved in other languages. In the case of python, we can, via the use of the "magic" of decorators remove the setup logic from the test function itself, and move it outside as a "pre-test" step.@scenario('three users with user1 and user2 sharing a team')
def test_is_on_same_team(d):
assertTrue(is_on_same_team(d.user1, d.user2)
assertFalse(is_on_same_team(d.user1, d.user3)
Although this improvement is basically just some syntactic sugar, the above code now is about as clear as you can get for separating what you are testing (that is_on_same_team works as expected) with what you need to have to perform said test.Isn't this just Behavior Driven Development?
No. This is a methodology for abstracting away what is necessary for a test from how to perform the test. Behavior Driven Development (BDD) is a way to abstract away the implementation of the testing from what it does via the use of a plaintext feature file. For example, we could convert the above test into a BDD version via the following:Given three users two of whom share a team
Then the users on the team should be considered on the same team
And the users not on the team should not be considered on the same team
Now, each of these steps would then be implemented in some other file along the lines of:
@given("three users two of whom share a team")
def three_users_two_of_whom_share_a_team():
d = scenario('three users with user1 and user2 sharing a team')
@then("the users on the team should be considered on the same team")
def users_on_same_team_asserts_true():
assertTrue(is_on_same_team(d.user1, d.user2)
@then("the users not on the team should not be considered on the same team")
def users_on_different_team_asserts_false():
assertFalse(is_on_same_team(d.user1, d.user3)
As such, the use of the testing methodology described here to abstract away what you need from what you have to test works perfectly well using BDD as well.Challenge accepted?
The challenge I put forth to everyone when starting a new project is to not skip on the testing infrastructure early on, but try to make your way through the phases as quickly as possible. This applies to both developer and non-technical person (e.g. CEO, Marketing Exec, etc.) alike. It takes a little bit of forethought, but you will save yourself countless hours of refactoring tests (which is a real time-sink!) and make you a much happier test writer... and everyone that has to review your code will love you for having such easy to read and understand tests.Tuesday, July 1, 2014
Elementium: Browser testing done right
Everyone's reaction to Selenium is the same. First, it's a sense of awe for how it can navigate around a website, enter text, and click on buttons. This is soon followed by frustration when finding out that unlike most code that has been written to develop the application, testing in browsers is particularly fickle; widgets don't load in the same number of seconds in different runs, and elements become stale when the DOM updates. This leads to a state of despair and generally results in brittle testing code that is littered with
time.sleep() calls and with ugly retry logic. This was exactly my progression through the journey of automated browser testing. Fortunately, it didn't end there.Before I continue, let me be very clear in saying that I am eternally grateful to the folks that created and maintain Selenium - I could not do any of the testing that I do without it. It is a fantastic library that does a fantastic job. It just happens to have been created in an era when dynamic pages littered with AJAX calls was not the norm. Any of this work or potential "criticisms" are meant to be taken with this in mind -- without Selenium, we wouldn't even be this far.
If one takes a look at what people try to accomplish with automated browser testing it is:
- Select browser elements, and perform actions based on selected elements
- Assert to insist that particular elements exist
- Waiting for an element to appear on the page
- Handling
StaleElementExceptions
spin_assertthat spins and waits until the element appears. Here is a modified version of what they present (but the basic idea is exactly the same):
def spin_assert_equal(element, assertion):
for i in xrange(60):
try:
# Re-get the element from the page via the lambda
# and assert they are equal
assert element() = assertion
return
except Exception, e:
pass
sleep(1)
# If we get here, give it one more try, or make it raise an
# AssertionError
assert element() = assertion
# Create a lambda that finds the element
element = lambda: selenium.find_element_by_id('foo').text
# Try the assertion
spin_assert(element, 'FOO')
For full details on their method, check out their post here. Does the above method work? Of course! But let me ask you this. Which of the following do you think is easier to read, understand, and maintain?
# Option 1: Pure selenium
element = lambda: selenium.find_element_by_id('foo').text
spin_assert(element, 'FOO')
# Option 2: Elementium
elements.find('#foo').insist(lambda e: e.text() == 'FOO')
I'm hoping that you say Option 2. Taking cues from jQuery, Elementium allows you to chain commands and handles all of the automatic retrying for you. For example, you could do something like this if you really wanted to:
elements.\
find('.foo').\
filter(lambda e: e.text().startswith('a')).\
until(lambda e: len(e) == 2).\
foreach(lamba e: e.click())
This here will find all elements with the CSS class 'foo', filter it down to the elements that have text starting with 'a', insist that there are exactly 2 of them, and then click on them. Yes, all of that in 1 line of code. Oh, and did I mention that this handles all of the retry logic automatically for you? (Btw, you can use the click() method on a list of elements as well elements.find('.foo').click())How about if you want to wait until there are exactly 3 elements of this type on the page (because, for example, you have made some AJAX calls that creates three notifications)
elements.find('.notification').until(lambda e: len(e) == 3)
That's it. This will retry (for 20 seconds by default) and wait until there are three elements with the CSS class 'nofitication'."How does it do all this magic," you ask. I won't go into too much detail here, but under the hood, each selector (e.g.
'.foo' or '#foo') is stored as a callback function (similar to the lambda: selenium.find_element_by_id('foo') of the first example. This way, when any of the calls to any of the methods of an element has an expected error (StaleElementException, etc.) it will recall this function. If you perform chaining, this will actually propagate that refresh (called update()) up the entire chain to ensure that all parts of the call are valid. Cool!
Ok, and now to a "full" example that shows you exactly how to set everything up so that you can start using this today.
from selenium import webdriver
from elementium.drivers.se import SeElements
# Initialize the elements wrapper
elements = SeElements(webdriver.Firefox())
# Do cool stuff
elements.find('#foo')
It's as simple as that!Although this library is still under development, you can get this library and make use of it now by getting it from this public GitHub repo: https://github.com/actmd/elementium. There you will find more usage examples, the code, etc. We have been using it at ACT.md for the past 3 months and it has reduced our testing code, made it more stable, and made it much more legible. I'd say that's a win, win, win situation!
Don't hesitate to let me know if you have any questions, suggestions, etc. Happy testing.
Tuesday, February 18, 2014
Running background tasks with Fabric
run() or sudo() command to run a long running test, we would have to make sure that we don't lose the connection between our local development machine and the AWS instance (otherwise the tests would just stop running). So, clearly, we want to start the long running task and then be able to go on our merry way.As you may know, there are several ways to start background jobs on a *NIX like machine: nohup, screen, etc. The issue is that running things in the background may cause some issues when using Fabric. Just take a look at the Fabric FAQ that covers this topic, along with this nice discussion.
The easiest solution that I found that works in pretty much all cases is to use the one suggested here using dtach. I slightly extended the suggested solution there, to be a bit more well-rounded and complete. As you can see, this uses
apt-get to install dtach, so if you are not running Ubuntu, make sure to update that appropriately.from fabric.api import run
from fabric.api import sudo
from fabric.contrib.files import exists
def run_bg(cmd, before=None, sockname="dtach", use_sudo=False):
"""Run a command in the background using dtach
:param cmd: The command to run
:param output_file: The file to send all of the output to.
:param before: The command to run before the dtach. E.g. exporting
environment variable
:param sockname: The socket name to use for the temp file
:param use_sudo: Whether or not to use sudo
"""
if not exists("/usr/bin/dtach"):
sudo("apt-get install dtach")
if before:
cmd = "{}; dtach -n `mktemp -u /tmp/{}.XXXX` {}".format(
before, sockname, cmd)
else:
cmd = "dtach -n `mktemp -u /tmp/{}.XXXX` {}".format(sockname, cmd)
if use_sudo:
return sudo(cmd)
else:
return run(cmd)
Capturing the output
Although the above snippet works perfectly fine if you are just running a background task and then wanting to forget about it, what happens if you want to capture the output from some command? The problem is that you can't just redirect the output of dtach like you would with nohup. The simplest solution that I could come up was to make dtach run a bash command and explicitly redirect the output. So, I have another function that helps me accomplish this.def run_bg_bash(
cmd, output_file=None, before=None, sockname="dtach", use_sudo=False):
"""Run a bash command in the background using dtach
Although bash commands can be run using the plain :func:`run_bg` function,
this version will ensure to do the proper thing if the output of the
command is to be redirected.
:param cmd: The command to run
:param output_file: The file to send all of the output to.
:param before: The command to run before the dtach. E.g. exporting
environment variable
:param sockname: The socket name to use for the temp file
:param use_sudo: Whether or not to use sudo
"""
if output_file:
cmd = "/bin/bash -c '{} > {}'".format(cmd, output_file)
else:
cmd = "/bin/bash -c '{}'".format(cmd)
return run_bg(cmd, before=before, sockname=sockname, use_sudo=use_sudo)
As you can see, all this does is that it wraps the command with an explicit call to bash which then is the one that interprets the output redirection. That's it! Happy dtaching!
Thursday, December 19, 2013
Viewing New Relic Audit Logs When Not Using Ruby
All you have to do is modify your newrelic.ini file as follows:
- Uncomment the line:
log_file = /tmp/newrelic-python-agent.log - Then set the log_level setting to
debug - Underneath the log_level line, add the following:
debug.log_data_collector_payloads = True debug.log_agent_initialization = True debug.log_data_collector_calls = True debug.log_transaction_trace_payload = True debug.log_thread_profile_payload = True debug.log_raw_metric_data = True - Restart your application that loads the New Relic agent.
tail -f /tmp/newrelic-python-agent.log
As you might have guessed, this will create a large log file, so make sure to turn off these settings when you are done with them (or ensure that you are properly rotating your logs).According to New Relic support, the
debug.log_data_collector_payloads setting is what will log every data message sent to the New Relic collector. As this data is encoded and compressed, the other settings above are what decode the data and print them out in human readable form.If you are not seeing any logging output in your log file, make sure that you are not running into any issues due to logger conflicts. For example, if you disable existing loggers in your app, you won't see any output. For further details take a look at this article put together by the folks at NewRelic.
Wednesday, August 14, 2013
Converting a TEXT column to a VARCHAR column in MySQL
For simplicity, let's assume you have a table that looks something like this:
# Our original table
CREATE TABLE my_table (
id INT NOT NULL AUTO_INCREMENT,
my_col TEXT NOT NULL,
PRIMARY KEY (id)
);
What you want to do is convert the my_col column into a VARCHAR column. The following steps should get you from TEXT to VARCHAR:
# Create a temp table
CREATE TABLE tmpvchar (
id INTEGER,
my_col_vchar VARCHAR(255)
);
# Copy the data over and cast it to a character
INSERT INTO tmpvchar (id, my_col_vchar)
SELECT id, CAST(my_col AS CHAR(255)) FROM my_table;
# Add a new column to the original table
ALTER TABLE my_table ADD my_col_vchar VARCHAR(255);
# Copy the data from the temp table
UPDATE my_table SET my_col_vchar = (SELECT my_col_vchar FROM tmpvchar WHERE my_table.id = tmpvchar.id);
# Remove the incorrect column
ALTER TABLE my_table DROP my_col;
# Rename the column back to the correct one
ALTER TABLE my_table CHANGE my_col_vchar my_col VARCHAR (255) NOT NULL;
# Drop the temporary table
DROP TABLE tmpvchar;
I hope this ends up saving someone some time!
Tuesday, July 30, 2013
Parsing Arguments in Python with argparse
The Simplest
First, let's start with just getting arguments from the command line.# argparse1.py
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='An awesome program')
parser.add_argument(
'first_name', help='First Name')
parser.add_argument(
'last_name', help='Last Name')
args = vars(parser.parse_args())
print "{} {}".format(args['first_name'], args['last_name'])
If you run this as python argparse1.py, without any parameters, you should see an error message with a helpful usage message along the lines of:
# argparse1.py
usage: argparse1.py [-h] first_name last_name
argparse1.py: error: too few arguments
I.e. just by using the argparse parser and parsing the arguments, we have a built in usage generator. Neat!Now, if you run this with actual values, such as
python argparse1.py John Smith, then, as you'd expect this will work work and print out "John Smith". One thing to note is that I used vars() to get the variables out of the Namespace that is created by the parser. If you want to, you can get the values directly out of the Namespace without using vars(), but I prefer the dictionary style access for my arguments. For more details, on this, I'll refer you to the python documentation. Named Parameters
While the above example works well for simple situations where all arguments are required and positional arguments make sense, it is often nice to allow the use of named (optional) parameters. For example, we can re-write the above using named parameters as follows:# argparse2.py
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='An awesome program')
parser.add_argument(
'--first_name', required=True, help='First Name')
parser.add_argument(
'--last_name', required=True, help='Last Name')
parser.add_argument(
'--middle_name', required=False, help='Middle Name')
args = vars(parser.parse_args())
if args['middle_name']:
print "{} {} {}".format(
args['first_name'], args['middle_name'], args['last_name'])
else:
print "{} {}".format(args['first_name'], args['last_name'])
Unlike before, we now have to specify the argument name before using it, but
we can get the same result as before by doing:
# argparse1.py
python argparse2.py --first_name John --last_name Smith
Just as before, if you don't provide first_name or last_name, we get a helpful error message. However, we also added a new optional argument for the middle name, which we can provide if we feel like it. E.g.
# argparse1.py
python argparse2.py --first_name John --middle_name Bob --last_name Smith
Sub Commands (Sub-Parsers)
Ok, now that we know the basics, let's look at the case where we have a program that has two different sub commands.# argparse3.py
import argparse
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='An awesome program')
subparsers = parser.add_subparsers(
title='subcommands', description='valid subcommands',
help='additional help')
parser_create = subparsers.add_parser('create')
parser_create.set_defaults(which='create')
parser_create.add_argument(
'--first_name', required=True, help='First Name')
parser_create.add_argument(
'--last_name', required=True, help='Last Name')
parser_delete = subparsers.add_parser('delete')
parser_delete.set_defaults(which='delete')
parser_delete.add_argument(
'id', help='Database ID')
args = vars(parser.parse_args())
if args['which'] == 'create':
print "Creating {} {}".format(args['first_name'], args['last_name'])
else:
print "Deleting {}".format(args['id'])
Whoa! What do we have here? If you run this without any arguments, you will see a help message along the lines of:
# argparse1.py
usage: argparse3.py [-h] {create,delete} ...
argparse3.py: error: too few arguments
This is telling us that we have to provide one of the available subcommands "create" or "delete". So, let's try that by running python argparse3.py create:
# argparse1.py
usage: argparse3.py create [-h] --first_name FIRST_NAME --last_name LAST_NAME
argparse3.py create: error: argument --first_name is required
Now we get the helpful message saying exactly what the arguments are for the subcommand "create". Neat! If you actually provide it with valid inputs, you will see that the parser only returns the arguments for the subparse that was selected. In other words, continuing with the previous exmaple, if we run the program as follows:
# argparse1.py
python argparse3.py create --first_name John --last_name Smith
Then we only get the arguments first_name and last_name; id will not be there since it didn't belong to any of the first sub-parser's arguments. As you also may have noticed, you can mix and match positional arguments and named arguments at will.Unfortunately, the one thing that is lacking by default in the argument parsing when using subcommands is a way to get which subcommand was run. Although in the example above we can figure it out since only the "create" subcommand has first_name and last_name, but what would we do if both subcommands had overlapping arguments? The solution to this (originally found here) is to provide a default argument that tells us which subcommand was chosen. This is why I added, for example, the line parser_create.set_defaults(which='create') to the first subparser. This allows us to get the argument "which" that we have added to tell us which subcommand was chosen.
Going Farther
Well, that's it for the basics. If you want to do more than this, then I highly suggest you read the docs as they contain other examples. Hopefully, this little introduction has made it a bit easier to digest what is going on in that documentation page!Friday, May 17, 2013
Unit testing your Flask REST Application
Unit Testing Is Not Optional
As it says on the Flask website, "something that is untested is broken." While this obviously isn't always true, there is no way for you to know whether or not you have bugs in your code unless you test it. When you initially start writing your application, you probably think one of the following:- Even if it takes only 10 minutes to write the unit test, that time could be spent writing the next great feature.
- Why bother wasting time writing test cases when one can just test the application by visiting the site and clicking on the various pages. Even with a REST API that doesn't have nice user interface, one could theoretically could use something like the Advanced REST Client Google Chrome plugin to inspect the JSON that is produced.
Unit testing is the notion of testing the parts (units) of your code to ensure that they behave as expected. For example, if you have some code that verifies whether a password is correct for a given user, then the unit test should test all possible scenarios that this function may encounter. For example, what happens when you pass None to your function? What happens when you provide an integer password instead of a string password that you were expecting? What happens when you provide the correct password but for a different user in the system?
By testing each of your components independently, you can prove to yourself (and others!) that everything is behaving as expected. So, if in the future you then change one of the components (say you now have a faster way of searching for users in your database given their first name), you merely have to test it with the code that you have already written to verify that it is doing what is expected. If it returns the same results as before (assuming the new method doesn't change the ordering, of course!) then you know that even though the underlying search algorithm changed, it's still behaving the same way as before. Since it's still behaving the same way as before, any other bit of code that required the searching of users by email will also work just like before. Now you can feel free to pat yourself on the back and sleep easy that night.
The key to good unit testing is to ensure that you truly test each unit independently. In other words, one test should not have an affect on another. For example, if you have a test for adding a new user to a database, that test should not affect the data being used for another test. Thus, it's imperative that you setup and initialize dependencies (like databases) in each of the tests so that you know exactly what the starting point of the test is. If this wasn't the case, you will run into problems when you try to run, for instance, a test for counting the number of users in your database.
Although, the examples that I am providing below are in the context of Flask application development, the principles of unit testing are framework and language independent.
Now that I hopefully convinced you that unit testing is not something that should be an afterthought, but something that should be part of your day-to-day programming habits, let's take a look at some unit testing for Flask application.
Unit Testing Examples
To get started, I would highly recommend the great introduction to the basics of unit testing Flask applications on the Flask website. The examples that I have linked below are for slightly more complicated or specific things that I had to test.Unit Testing Flask File Uploads Without Any Files
Since uploading files can be such a crucial part or a web application, it should be tested like any other part of the system. Unfortunately, performing this test without actually uploading a real file (i.e. simulating the entire thing) is something that isn't as straightforward as I initially expected (now that I know how to do it, it's really easy!).
In any case, the code below simulates uploading a file using StringIO and then simulates the FileStorage used by Flask (and Werkzeug) by returning a "mocked" TestingFileStorage of our choosing.
from StringIO import StringIO
import unittest
from flask import Request
from werkzeug import FileStorage
from werkzeug.datastructures import MultiDict
# Import your Flask app from your module
from myapp import app
class FlaskAppUploadFileTestCase(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['CSRF_ENABLED'] = False
self.app = app
# .. setup any other stuff ..
def runTest(self):
# Loop over some files and the status codes that we are expecting
for filename, status_code in \
(('foo.png', 201), ('foo.pdf', 201), ('foo.doc', 201),
('foo.py', 400), ('foo', 400)):
# The reason why we are defining it in here and not outside
# this method is that we are setting the filename of the
# TestingFileStorage to be the one in the for loop. This way
# we can ensure that the filename that we are "uploading"
# is the same as the one being used by the application
class TestingRequest(Request):
"""A testing request to use that will return a
TestingFileStorage to test the uploading."""
@property
def files(self):
d = MultiDict()
d['file'] = TestingFileStorage(filename=filename)
return d
self.app.request_class = TestingRequest
test_client = self.app.test_client()
rv = test_client.post(
'/files',
data=dict(
file=(StringIO('Foo bar baz'), filename),
))
self.assertEqual(rv.status_code, status_code)
Let's take a look at this code in a bit more detail. The first thing we do in our runTest() method is loop over 5 different file types. The assumption is that our application accepts the first 3, while rejecting the last 2. In the loop, we create a class TestingRequest that we will use as our request class for our application. What this does, is it overrides the files attribute to return a TestingFileStorage (defined below) instead of the FileStorage that is normally returned. As I mentioned in the comments, we are creating this class inside the for loop because we need to set the filename that is returned by the TestingFileStorage equal to the one that we are currently using in the loop.Now that we have created our custom Request, we tell the Flask app to use ours instead and then create a TestClient. Note, you must set the request_class of the app before you create the TestClient. Using the patched TestClient, we can the POST a "file" as normal. Except instead of using a real file, we use a StringIO object so that we don't actually have to have any random files in our project for testing.
That's it really, using the above code (and the TestingFileStorage below) you should be able to test your file uploading routes without actually having to have any files on disk!
I left the implementation of the TestingFileStorage until the end because I copied and pasted it from the Flask-Uploads extension. So that you don't have to got digging around the source code there, I've copied it here for your reference. Enjoy.
class TestingFileStorage(FileStorage):
"""
This is a helper for testing upload behavior in your application. You
can manually create it, and its save method is overloaded to set `saved`
to the name of the file it was saved to. All of these parameters are
optional, so only bother setting the ones relevant to your application.
This was copied from Flask-Uploads.
:param stream: A stream. The default is an empty stream.
:param filename: The filename uploaded from the client. The default is the
stream's name.
:param name: The name of the form field it was loaded from. The default is
``None``.
:param content_type: The content type it was uploaded as. The default is
``application/octet-stream``.
:param content_length: How long it is. The default is -1.
:param headers: Multipart headers as a `werkzeug.Headers`. The default is
``None``.
"""
def __init__(self, stream=None, filename=None, name=None,
content_type='application/octet-stream', content_length=-1,
headers=None):
FileStorage.__init__(
self, stream, filename, name=name,
content_type=content_type, content_length=content_length,
headers=None)
self.saved = None
def save(self, dst, buffer_size=16384):
"""
This marks the file as saved by setting the `saved` attribute to the
name of the file it was saved to.
:param dst: The file to save to.
:param buffer_size: Ignored.
"""
if isinstance(dst, basestring):
self.saved = dst
else:
self.saved = dst.name
REST App Response Status Code Testing Harness
class SomeRouteTestCase(FlaskAppRouteStatusCodeTestCase):
"""Test for /foo"""
__GET_STATUS_CODES__ = dict(
user=200,
admin=200,
super_user=200
)
__PUT_STATUS_CODES__ = dict(
user=403,
admin=200,
super_user=200
)
def get(self, user, test_data, db_data):
self.login(user.email, user.password)
rv = self.app.get('/foo')
self.logout()
return rv
def post(self, user, test_data, db_data):
self.login(user.email, user.password)
rv = self.app.post(
'/foo',
data=json.dumps(dict(bar="barbar", bam="bambam")),
content_type='application/json')
self.logout()
@unittest.skip("No PATCH")
def test_patch(self):
"""Override the PATCH tester since /foo can't be patched."""
pass
What kind of magic is this? Well, not any kind, really. A TestCase for a particular route simply defines the correct status codes for the various HTTP methods and the code to make the calls. In the example above, anyone should be able to perform GET /foo (all of them have a response code of 200), while only admins and super_users are allowed to POST. Since this route doesn't accept the PATCH method, we are telling unittest to skip it with the @unittest.skip decorator. Using this framework, one can test any route with all types of users with minimal effort.In order to make this all work, we have to define the FlaskAppRouteStatusCodeTestCase that this TestCase inherits from. Fortunately for you, the basic structure of it is in the gist below and pretty straight forward. If you simply fill out the methods that initialize the database and get the set of users to perform the testing on, you can create tests for your routs with reckless abandon.
Although I have only added the methods in FlaskAppRouteStatusCodeTestCase for GET, POST, and PATCH, it should be trivial to add in any other methods for things like DELETE, PUT, etc. For your viewing pleasure, I've included the full example here:



