<commit_before><commit_msg>First version of this post I feel comfortable with<commit_after>{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "I've been thinking a lot about software achitecure lately. Not just thinking, because I wouldn't come up with these ideas on my own, but consuming a lot about it -- books, talks, slide decks, blog posts. And while thinking about all this, I've been hacking away at some projects in my spare time. And I noticed something, there's a lot of things in these projects that look a lot like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@app.route('/register', methods=['GET', 'POST'])\n",
    "def register():\n",
    "    form = RegisterUserForm()\n",
    "    \n",
    "    if form.validate_on_submit():\n",
    "        user = User()\n",
    "        form.populate_obj(user)\n",
    "        db.session.add(user)\n",
    "        db.session.commit()\n",
    "        return redirect('homepage')\n",
    "    \n",
    "    return render_template('register.html', form=form)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is a bog standard user registration endpoint. We create a form, check if it's valid, shove that information on a user model and then into the database and redirect off. If it's not valid or if it wasn't submitted (the user just navigated to the page), we render out some HTML.\n",
    "\n",
    "It's all very basic, well trodden code. Besides, who wants to do registration again? It's boring. We want to do the interesting stuff. But there's some very real consequences to this code: \n",
    "\n",
    "## It's not testable\n",
    "\n",
    "Everything is wrapped up together, form validation, database stuff, rendering. Honestly, I'm not interested in testing if SQLAlchemy, WTForms of Jinja2 work -- they have their own tests. So testing this ends up looking like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@mock.patch('myapp.views.RegisterUserForm')\n",
    "@mock.patch('myapp.views.db')\n",
    "@mock.patch('myapp.views.redirect')\n",
    "@mock.patch('myapp.views.url_for')\n",
    "@mock.patch('myapp.views.render_template')\n",
    "def test_register_new_user(render, url_for, redirect, db, form):\n",
    "    # TODO: Write test\n",
    "    assert True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What's even the point of this? We're just testing if Mock works at this point. There's actual things we can do to make it more testable, but before delving into that, \n",
    "\n",
    "## It hides logic\n",
    "\n",
    "If registering a user was solely about, \"Fill this form out and we'll shove it into a database\" there wouldn't be a blog post here. However, there is some logic hiding out here in the form:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class RegisterUserForm(Form):\n",
    "    def validate_username(self, field):\n",
    "        if User.query.filter(User.username == field.data).count():\n",
    "            raise ValidationError(\"Username in use already\")\n",
    "    \n",
    "    def validate_email(self, field):\n",
    "        if User.query.filter(User.email == field.data).count():\n",
    "            raise ValidationError(\"Email in use already\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we call `RegisterUserForm.validate_on_submit` it also runs these two methods. However, I'm not of the opinion that the form should talk to the database at all, let alone run validation against database contents. So, let's write a little test harness that can prove that an existing user with a given username and email causes us to not register:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from myapp.forms import RegisterUserForm\n",
    "from myapp.models import User\n",
    "\n",
    "from collections import namedtuple\n",
    "\n",
    "from unittest import mock\n",
    "\n",
    "FakeData = namedtuple('User', ['username', 'email', 'password', 'confirm_password'])\n",
    "\n",
    "def test_existing_username_fails_validation():\n",
    "    test_data = FakeData('fred', '<EMAIL>', 'a', 'a')\n",
    "    UserModel = mock.Mock()\n",
    "    UserModel.query.filter.count.return_value = 1\n",
    "    form = RegisterUserForm(obj=test_data)\n",
    "    \n",
    "    with mock.patch('myapp.forms.User', UserModel):\n",
    "        form.validate()\n",
    "    \n",
    "    assert form.errors['username'] == \"Username in use already\"\n",
    "    \n",
    "def test_existing_email_fails_validation():\n",
    "    test_user = FakeUser('fred', '<EMAIL>', 'a', 'a')\n",
    "    UserModel = mock.Mock()\n",
    "    UserModel.query.filter.first.return_value = True\n",
    "    form = RegisterUserForm(obj=test_user)\n",
    "    \n",
    "    with mock.patch('myapp.forms.User', UserModel):\n",
    "        form.validate()\n",
    "    \n",
    "    assert form.errors['username'] == \"Email in use already\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If these pass -- which they should, but you may have to install `mock` if you're not on Python 3 -- I think we should move the username and email validation into their own callables that are independently testable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def is_username_free(username):\n",
    "    return User.query.filter(User.username == username).count() == 0\n",
    "\n",
    "def is_email_free(email):\n",
    "    return User.query.filter(User.email == email).count() == 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And then use these in the endpoint itself:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@app.route('/register', methods=['GET', 'POST'])\n",
    "def register():\n",
    "    form = RegisterUserForm()\n",
    "    \n",
    "    if form.validate_on_submit():\n",
    "        if not is_username_free(form.username.data):\n",
    "            form.errors['username'] = ['Username in use already']\n",
    "            return render_template('register.html', form=form)\n",
    "        \n",
    "        if not is_email_free(form.email.data):\n",
    "            form.errors['email'] = ['Email in use already']\n",
    "            return render_template('register.html', form=form)\n",
    "        \n",
    "        user = User()\n",
    "        form.populate_obj(user)\n",
    "        db.session.add(user)\n",
    "        db.session.commit()\n",
    "        return redirect('homepage')\n",
    "    \n",
    "    return render_template('register.html', form=form)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This is really hard to test, so instead of even attempting that -- being honest, I spent the better part of an hour attempting to test the actual endpoint and it was just a complete mess -- let's extract out the actual logic and place it into it's own callable:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class OurValidationError(Exception):\n",
    "    def __init__(self, msg, field):\n",
    "        self.msg = msg\n",
    "        self.field = field\n",
    "\n",
    "def register_user(username, email, password):\n",
    "    if not is_username_free(username):\n",
    "        raise OurValidationError('Username in use already', 'username')\n",
    "    \n",
    "    if not is_email_free(email):\n",
    "        raise OurValidationError('Email in use already', 'email')\n",
    "    \n",
    "    user = User(username=username, email=email, password=password)\n",
    "    db.session.add(user)\n",
    "    db.session.commit()\n",
    "    \n",
    "    \n",
    "@app.route('/register', methods=['GET', 'POST'])\n",
    "def register_user_view():\n",
    "    form = RegisterUserForm()\n",
    "    \n",
    "    if form.validate_on_submit():\n",
    "        try:\n",
    "            register_user(form.username.data, form.email.data, form.password.data)\n",
    "        except OurValidationError as e:\n",
    "            form.errors[e.field] = [e.msg]\n",
    "            return render_template('register.html', form=form)\n",
    "        else:\n",
    "            return redirect('homepage')\n",
    "        \n",
    "    return render_template('register.html', form=form)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we're beginning to see the fruits of our labors. These aren't the easiest functions to test, but there's less we need to mock out in order to test the actual logic we're after."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def test_duplicated_user_raises_error():\n",
    "    ChasteValidator = mock.Mock(return_value=False)\n",
    "    \n",
    "    with mock.patch('myapp.logic.is_username_free', ChasteValidator):\n",
    "        with pytest.raises(OurValidationError) as excinfo:\n",
    "            register_user('fred', '<EMAIL>', '<PASSWORD>')\n",
    "    \n",
    "    assert excinfo.value.msg == 'Username in use already'\n",
    "    assert excinfo.value.field == 'username'\n",
    "\n",
    "def test_duplicated_user_raises_error():\n",
    "    ChasteValidator = mock.Mock(return_value=False)\n",
    "    PromisciousValidator = mock.Mock(return_value=True)\n",
    "    \n",
    "    with mock.patch('myapp.logic.is_username_free', PromisciousValidator),\n",
    "         mock.patch('myapp.logic.is_email_free', ChasteValidator):\n",
    "        with pytest.raises(OurValidationError) as excinfo:\n",
    "            register_user('fred', '<EMAIL>', '<PASSWORD>')\n",
    "    \n",
    "    assert excinfo.value.msg == 'Email in use already'\n",
    "    assert excinfo.value.field == 'email'\n",
    "\n",
    "def test_register_user_happy_path():\n",
    "    PromisciousValidator = mock.Mock(return_value=True)\n",
    "    MockDB = mock.Mock()\n",
    "    \n",
    "    with mock.patch('myapp.logic.is_username_free', PromisciousValidator),\n",
    "         mock.patch('myapp.logic.is_email_free', ChasteValidator), \n",
    "         mock.patch('myapp.logic.db', MockDB):\n",
    "    \n",
    "        register_user('fred', '<EMAIL>', '<PASSWORD>')\n",
    "    \n",
    "    assert MockDB.commit.call_count"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, we should also write tests for the controller. I'll leave that as an exercise. However, there's something very important we're learning from these tests. We have to `mock.patch` everything still. Our validators lean directly on the database, our user creation leans directly on the database, everything leans directly on the database. And I don't want to do that, we've found that it makes testing hard. We're also seeing if we need to add another registration restriction -- say we don't like people named Fred so we won't let anyone register with a username or email containing Fred in it -- we need to crack open the `register_user` function and add it directly. We can solve both of these problems.\n",
    "\n",
    "## The Database Problem\n",
    "\n",
    "To address the database problem we need to realize something. We're not actually interested in the database, we're interested in the data it stores. And since we're interested in finding data rather than where it's stored at, why not stuff an interface in the way?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from abc import ABC, abstractmethod\n",
    "\n",
    "class AbstractUserRepository(ABC):\n",
    "    \n",
    "    @abstractmethod\n",
    "    def find_by_username(self, username):\n",
    "        pass\n",
    "    \n",
    "    @abstractmethod\n",
    "    def find_by_email(self, email):\n",
    "        pass\n",
    "    \n",
    "    @abstractmethod\n",
    "    def persist(self, user):\n",
    "        pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hmm...that's interesting. Since we'll end up depending on this instead of a concrete implementation, we can run our tests completely in memory and production on top of SQLAlchemy, Mongo, a foreign API, whatever.\n",
    "\n",
    "But we need to inject it into our validators instead of reaching out into the global namespace like we currently are."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def is_username_free(user_repository):\n",
    "    def is_username_free(username):\n",
    "        return not user_repository.find_by_username(username)\n",
    "    return is_username_free\n",
    "\n",
    "def is_email_free(user_repository):\n",
    "    def is_email_free(email):\n",
    "        return not user_repository.find_by_email(email)\n",
    "    return is_email_free"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These validators are simple enough that closures work instead of full-fledged objects. The important part here is to maintain a consistent interface -- if we need to use classes all of a sudden, we need to define a `__call__` on them to maintain this interface.\n",
    "\n",
    "We can also change our register callable to accept the repository as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def register_user(user_repository):\n",
    "    email_checker = is_email_free(user_repository)\n",
    "    username_checker = is_username_free(user_repository)\n",
    "    \n",
    "    def register_user(username, email, password):\n",
    "        \n",
    "        if not username_checker(username):\n",
    "            raise OurValidationError('Username in use already', 'username')\n",
    "\n",
    "        if not email_checker(email):\n",
    "            raise OurValidationError('Email in use already', 'email')\n",
    "\n",
    "        user = User(username=username, email=email, password=password)\n",
    "        user_repository.persist(user)\n",
    "        \n",
    "    return register_user"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course the tests break now, and that's okay. We made a *very* sweeping change to the architecture here. We need to go back through and alter the tests one by one, but instead of patching everything out we can do something better: Dependency Injection."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def test_duplicated_email_causes_false():\n",
    "    fake_user_repository = mock.create_autospec(AbstractUserRepository)\n",
    "    fake_user_repository.find_by_email.return_value = True\n",
    "    checker = is_email_free(fake_user_repository)\n",
    "    \n",
    "    assert not checker('<EMAIL>')\n",
    "    \n",
    "def test_duplicated_username_causes_false():\n",
    "    fake_user_repository = mock.create_autospec(AbstractUserRepository)\n",
    "    fake_user_repository.find_by_username.return_value = True\n",
    "    checker = is_username_free(fake_user_repository)\n",
    "    \n",
    "    assert not checker('fred')\n",
    "\n",
    "\n",
    "def test_register_user_happy_path():\n",
    "    fake_user_repository = mock.create_autospec(AbstractUserRepository)\n",
    "    fake_user_repository.find_by_email.return_value = False\n",
    "    fake_user_repository.find_by_username.return_value = False\n",
    "    registrar = register_user(fake_user_repository)\n",
    "    \n",
    "    registrar('fred', '<EMAIL>', '<PASSWORD>')\n",
    "    \n",
    "    assert fake_user_repository.persist.call_count"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But to test that our validators function correctly in this context, we need to fake out `find_by_email` and `find_by_username` indpendently. This is a symptom of our code not being Open-Closed.\n",
    "\n",
    "## The Open-Closed Problem\n",
    "\n",
    "Revisiting the other major issue from how the code is laid out right now is that it's not Open-Closed. If you're not familiar with the principle, Wikipedia says this:\n",
    "\n",
    "> \"software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification\"\n",
    "\n",
    "Or in a different way, \"You should be able to change functionality without editing existing code.\" -- I believe I need to credit <NAME> with this, but I'm not sure. We've actually already used this idea by injecting the User Repository. In tests, we inject a fake or in memory repository, but in production it can be a SQLAlchemy implementation, or maybe wrap that up into a caching repository. We can do the same thing with the validators."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def register_user(user_repository, validator):\n",
    "    def registrar(username, email, password):\n",
    "        user = User(username, email, password)\n",
    "        validator(user)\n",
    "        user_repository.persist(user)\n",
    "    return registrar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course, our tests break again, so let's revisit the currently breaking one first:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def test_register_user_happy_path():\n",
    "    fake_user_repository = mock.create_autospec(AbstractUserRepository)\n",
    "    registrar = register_user(fake_user_repository, lambda user: None)\n",
    "    \n",
    "    registrar('fred', '<EMAIL>', '<PASSWORD>')\n",
    "    \n",
    "    assert fake_user_repository.persist.call_count\n",
    "    \n",
    "def test_register_user_fails_validation():\n",
    "    fake_user_repository = mock.create_autospec(AbstractUserRepository)\n",
    "    fake_validator = mock.Mock(side_effect=OurValidationError('username in use already', 'username'))\n",
    "    registrar = register_user(fake_user_repository, fake_validator)\n",
    "    \n",
    "    try:\n",
    "        registrar('fred', '<EMAIL>', '<PASSWORD>')\n",
    "    except OurValidationError as e:\n",
    "        assert e.msg == 'username in use already'\n",
    "        assert e.field == 'username'\n",
    "    else:\n",
    "        assert False, \"Did not Raise\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll need to tweak the validation logic some to make up for the fact that we're passing the whole user object now:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def validate_username(user_repoistory):\n",
    "    def validator(user):\n",
    "        if not user_repoistory.find_by_username(user.username):\n",
    "            raise OurValidationError('Username in use already', 'username')\n",
    "        return True\n",
    "    return validator\n",
    "\n",
    "def validate_email(user_repoistory):\n",
    "    def validator(user):\n",
    "        if not user_repoistory.find_by_email(user.email):\n",
    "            raise OurValidationError(\"Email in use already\", 'email')\n",
    "        return True\n",
    "    return validator"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The tests for these are pretty straight forward as well, so I'll omit them. But we need a way to stitch them together..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def validate_many(*validators):\n",
    "    def checker(input):\n",
    "        return all(validator(input) for validator in validators)\n",
    "    return checker"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And then hook it all up like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "validator = validate_username(validate_email(user_repository), validate_username(user_repository))\n",
    "registrar = register_user(user_repository, validator)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Our neglected Controller\n",
    "\n",
    "We've spent a lot of time looking at how to compartmentalize the registration logic and portion out its concerns. However, the controller itself needs some attention as well. When we last left, it looked like this:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "@app.route('/register', methods=['GET', 'POST'])\n",
    "def register_user_view():\n",
    "    form = RegisterUserForm()\n",
    "    \n",
    "    if form.validate_on_submit():\n",
    "        try:\n",
    "            register_user(form.username.data, form.email.data, form.password.data)\n",
    "        except OurValidationError as e:\n",
    "            form.errors[e.field] = [e.msg]\n",
    "            return render_template('register.html', form=form)\n",
    "        else:\n",
    "            return redirect('homepage')\n",
    "        \n",
    "    return render_template('register.html', form=form)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But we can do beter than that. The problem here is that the logic is set in stone, nested flows of control. But mostly, I really like any excuse to use class based views."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class RegisterUser(MethodView):\n",
    "    def __init__(self, form, registrar, template, redirect):\n",
    "        self.form = form\n",
    "        self.registrar = registrar\n",
    "        self.template = template\n",
    "        self.redirect = redirect\n",
    "    \n",
    "    def get(self):\n",
    "        return self._render()\n",
    "    \n",
    "    def post(self):\n",
    "        if self.form.validate_on_submit():\n",
    "            return self._register()\n",
    "        else:\n",
    "            return self._render()\n",
    "    \n",
    "    def _register(self):\n",
    "        try:\n",
    "            self.registrar(self.form.username.data, self.form.email.data, self.form.password.data)\n",
    "        except OurValidationError as e:\n",
    "            self._handle_error(e)\n",
    "            self._render()\n",
    "        else:\n",
    "            return self._redirect()\n",
    "    \n",
    "    def _render(self):\n",
    "        return render_template(self.template, self.form=form)\n",
    "\n",
    "    def _redirect(self):\n",
    "        return redirect(url_for(self.redirect))\n",
    "    \n",
    "    def _handle_error(self, e):\n",
    "        self.form.error[e.field] = [e.msg]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now *that* looks like a lot of code. However, each piece is much simpler than the original function. This speaks to handing out actions, as if it were *controlling* things. We can test the main logic of it as well. Even though we *should* test it, I might just leave it alone. Maybe run it through an acceptance test.\n",
    "\n",
    "## What did we gain?\n",
    "\n",
    "Everything is much more high level. The controller, validation, registration, even the form. Nothing's concerned with more than it needs to be. Sure, there's still some plumbing to do with the SQLAlchemy implementation of the `UserRepository`"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.4.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}

{'fraction_non_alphanumeric': 0.15536365076849945, 'fraction_numerical': 0.0011091744572967834, 'mean_word_length': 2.636560069144339, 'pattern_counts': {'":': 192, '<': 19, '<?xml version=': 0, '>': 20, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4812159', 'n_tokens_mistral': 8244, 'n_tokens_neox': 8075, 'n_words': 2166}