Skip to content

python

The one piece of data Discogs doesn’t have

As a record collector, Discogs is invaluable. What started as a crowd sourced website to catalog electronic music, it now has a catalog of almost every piece of recorded music. They’ve also added robust e-commerce abilities so it makes buying and selling easy - whether it’s for something popular or a rare record only made in Greece.

It can take a little work to figure out which record you have, especially if it’s something popular like Fleetwood Mac’s Rumours, which has literally hundreds of different releases, some of which come down to figuring out the engraving on the inside of the record to figure out which pressing plant made it.

The other thing that makes Discogs great is they have a Developer API available, making it easy to interact with all of the data they have stored programmatically. All of the data is available in JSON and there is also a Python library available, making it even easier.

The amount of data about each album and each of its variants is amazing. I won’t go through all of it and you can get an overview here , but it includes almost everything you could probably think of off the top of your head.

Each album release has a “master” release, that represent a set of similar releases. If an album is released this Friday, for example, and comes out on CD, cassette and vinyl, the master release would catalog each of these individually. Each individual one is a “release”. (And here’s to hoping Discogs changes the word master sometime in the near future).

Here is the master release page for Pearl Jam’s Ten, released in 1991:

Pearl Jam Ten Master Release

You can also see there are 251 different versions (or releases) of Ten on different formats. The list is currently sorted by chronological year of release and if we scroll all the way down to 2009, we can find the repress I own on vinyl. You can click through each individual release to see how it’s unique or find that particular version for sale. Clicking through we see:

Pearl Jam Vinyl Repress Release

There is one thing I am looking for to build one part of my app: What date was an album released? Neither the master release or the release of the album I own supplies anything but the year, not the full date, which is what I want to know to celebrate an album’s anniversary.

That’s by design. If you check out Discogs' Submission Guidelines , only the year is required. If you know the actual date, great, if you know the year and month, you can also input that.

I also found a forum post from a few years ago, which I can’t find now, where the Discogs developers stated they wouldn’t change the release date field to require a full year / month / day and they didn’t want to change the database schema to support that (which I don’t blame them).

But that doesn’t help me in building a web app that shows which albums were released today in history.

In addition to my pandemic induced loss of motivation, this has blocked me from moving forward. I have a couple of potential ideas to move forward.

The first is interfacing with the MusicBrainz API in addition to Discogs. MusicBrainz is a full open and free music database licensed under Creative Commons. They even link back to Discogs to make it easy to link releases. My thought is if Discogs returns a date field that doesn’t include the month and day, to then query MusicBrainz to see if they have it. I’m a little worried about responsiveness in making two queries like that, plus dealing with two different JSON schemas and matching.

The second idea may work for the majority of releases, but there is a catch at the end. Doing a random check of albums, when you look at a master release, the very first release chronologically almost always has the full date attached. I could query the API, and slice the list returned and use the date provided in the first entry in the list. But I checked one random album I had that I knew is an obscure release (Maggie’s Dream if you’re curious) and there is no date.

So I think what I’m going to do - as I need to get going and do something - is to build out and test the second option. This keeps me with just one API to learn in the short term and I can probably build a query pretty easily that will sort all of my releases by chronological order (using the trick above) that will show me how many albums don’t have a full release date. 10%? More? Less? Might as well build it out and get something done!

Stupid Pandemic

The COVID-19 pandemic has changed a lot of things for a lot of people. One of the things it’s done to me is made it harder to focus on my coding projects. (Part of that is I’m very deadline driven, and well, no deadlines on personal projects…)

Looking back on this year, I’ve actually accomplished a lot, but just not on the coding side. I’m just getting back to it and I have two projects I’m going to make progress on. The first one is my music centric web app built with Pyramid that interacts with my Discogs profile via the Discogs API. The second project involves a Raspberry Pi, but I’m much better at the coding side of that than I am on the hardware side. (And that’s saying something with my novice coding skills).

Introducing Silver Saucer

I’ve started my next Python project: Silver Saucer, which will be hosted at silversaucer.com. I originally bought the domain about ten years ago when I was thinking of going into business for myself, which never happened and I hung onto the domain name because I liked it. It was inspired by Neil Gaiman’s poem, The Day The Saucers Came, which I also have a framed art print of:

{{< figure src="day-the-saucers-came.jpg" title="The Day the Saucers Came" link="day-the-saucers-came-original.jpeg" >}}

Once upon a time, I was also a big fan of The X-Files, so it seems fitting.

With the pandemic here, I’ve had a little time to work on some long dormant projects, this being one of them. I’m stuck on another Python project (more because of the hardware than the coding), so I thought I’d find some time to work on this.

I have a few different goals of things I want to learn and practice:

Pyramid

It’s my third project using the Pyramid web framework. I don’t have an urge to learn Flask or another framework - I would like to get better at Pyramid, which I know a little. I also really like the Pyramid community. The few times I’ve become stuck and asked for help in the Pyramid IRC channel, they’ve been both welcoming and helpful. My first two Pyramid projects were based on the first training Talk Python offered for Pyramid, which used a package called pyramid_handlers which is no longer the recommend way to build a web app in Pyramid. I’m doing it the recommended way this time, using a class based approach.

Bootstrap and CSS

I know a little of HTML, enough to get by. But CSS and Bootstrap, not so much. I’ve already integrated a Bootstrap theme and tweaked it where it’s almost working, but I’m just hacking at it - I don’t really know what I’m doing and it’s something I want to get better at. I should probably find some good HTML / CSS tutorials and go through those.

Discogs API Integration

The goal for Silversaucer.com is to integrate with the Discogs API). Discogs.com is a website that lets you catalog your record collection and also includes community features and a marketplace where you can buy and sell records (or CDs or almost any kind of media). There are two things I want to build using the Discogs API:

Play a random record

I know that Discogs already has a feature on their website where you can have it randomly choose an item in your collection and if you shake the mobile app it will also show you a random item in your collection. I want to take that to the next level and sort by type (record, 45, CD, etc.).

On this day

The second things I want to build is a page that shows all records released for today’s date. This one is going to be more complicated and I’ll share more in a separate blog post.

Testing

I’ve blogged about it before, but I struggle to learn pytest. I’m going to continue to try and learn more about software testing, starting with this app.

Infrastructure

I’ve already hooked up Silver Saucer to Azure Pipelines to automatically do continuous integration. I want to integrate pytest and code coverage next. After that I may look into continuous delivery, but there’s is a lot about Azure that I don’t know.

Plenty of things to learn and keep me busy during these interesting times. Here's a teaser for making it to the end:

{{< figure src="silversaucer.png" title="Silver Saucer screenshot" link="silversaucer.png" >}}

Another Reason to Love Open Source

I've been using free and open source software (FOSS) for over twenty years and I've involved in with different FOSS projects for ten to fifteen years (on and off). Thare are so many different aspects of the open source community that I love (community being a big one to start with) and here's another example.

I'm trying to build a new website using Python and the Pyramid framework. I want to interact with APIs on the Discogs website. Discogs is a website and community that allows you to catalog your music and also buy and sell music. For me, this is all about my record collection. I'll be posting more about what I want to do, but right now I'm just trying to learn what my options are with the Discogs API and how to connect and interact with their API. I'm struggling a little bit to get authentication working.

I'm also using Postman, an application that makes it easy to test different APIs and see how they return data. I first learned about Postman through the Talk Python trainings (another reason to love the Talk Python trainings) and it was a core tool in building NFLPool and MLBPool to understand how MySportsFeeds showed sports data to integrate into those two apps.

If I'm struggling to authenticate and login to the Discogs API, I'm never going to get any results.

And here is why I love open source: Github user leopuleo has a Discogs-Postman repository where he has taken the time to not only put together almost 60 different ways to use the Discogs API, it also includes documentation and tests!

Postman Discogs

Postman Discogs Tests

This is all licensed under the MIT License, giving you the ability to take it and modify as you need to. leopuleo didn't have to do this, nor did he have to share it, or give it a license that just gives it away and let's you do anything you want with it. This is just one reason why open source is awesome - someone builds something, shares it with the world, and even better, let's you build on top of it.

Open source and free software rock.

Azure DevOps Python SQLite Issue - Part 4

It turns out that all the problems I was having for about ten days in trying to learn Azure Pipelines for continuous integration that I briefly touched on in Part 2 had nothing to do with me. There was a bug in Azure’s hosted images of Ubuntu used in the CI pipeline that did not include SQLite, as Python lists it as an optional module.

It’s good to know I’m not crazy and I did set it up right and this answers the question of why my builds would work on Python 3.6 but not Python 3.7. Kudos to Microsoft for the detailed post-mortem outlining the problem, what they did to fix it, and what they’ll do in the future to avoid something like this happening again.

In other news, I was distracted for a week to help a co-worker out with my first hardware project. I wrote a few lines of code to turn on and off a small pump that is controlled by a Raspberry Pi. I even wrote a test for it!

Lastly, I almost have CodeCov.io integrated into my CI. I make a commit, Azure Pipelines builds it and runs the tests, and the coverage report generated from the build is uploaded to CodeCov.io.

Next I’ll finally start going through some of Talk Python training courses and use a TDD approach to write tests for what I’m learning.

Finishing Setup (with Dependabot and Pytest reporting) and Project Goals - Part 3

Using Dependabot to manage Python dependencies

All I want is to start coding. I want to learn how to write a Pyramid app the correct way and also to start learning pytest. There's just one more thing to do and one to tweak in Azure and then it's time for the fun part - the actual learning and coding.

First, I’m going to setup Dependabot to help manage my Python dependencies.

Last year I bought a Python bundle on Humble Bundle that included a one year subscription to Pyup.io. I’ve been using it for both NFLPool and MLBPool2 as it’s free for open source projects (both of those projects are licensed under a MIT license). I haven’t decided what license the content on SilverSaucer.com, but knowing me, it will probably be open source. But on the off chance it’s not, I’m going to try out Dependabot.

Github recently purchased Dependabot and made it free. Visit Dependabot, sign in with your Github profile and link which repositories you want Dependabot to help manage.

That’s it - Dependabot will keep an eye on your requirements.txt and send you Pull Requests when it finds a new version of a Python module for you to merge.

As I’ve integrated with Azure Pipelines for continuous integration, it builds the pull request and tells me if it failed or succeeded, which is nice to know before I merge it:

Dependabot Details

But of the five pull requests Dependabot created, three of the five failed to build in Azure Pipelines:

Dependabot failed builds

And it’s the exact same error as I ran into in part 2 - the builds in Python 3.6 work and they don’t in 3.7 due to some kind of (and I’m guessing) SQLAlchemy / pysqlite3 problem. (Log)

After even more troubleshooting, and if I had any hair (which I don’t, thankfully) I would have pulled it out. I finally gave up and just merged them all. It built locally and I figured future me could deal with it. And then all the builds worked again. I have no idea why. I don’t know how real developers do this stuff. But it’s working now, even with a couple smaller commits to update the README.

But when it does work, it feels great. I had updated the README, which kicked off a build, and since I hooked up Slack to Azure Pipelines, I saw this:

Azure working build

So now it’s setup just like the Test-Driven Development with Python says it should be. From Chapter 24:

“As our site grows, it takes longer and longer to run all of our functional tests. If this continues, the danger is that we’re going to stop bothering. Rather than let that happen, we can automate the running of functional tests by setting up a “Continuous Integration” or CI server. That way, in day-to-day development, we can just run the FT that we’re working on at that time, and rely on the CI server to run all the tests automatically and let us know if we’ve broken anything accidentally. The unit tests should stay fast enough that we can keep running them every few seconds.”

Excerpt From: Harry Percival. “Test-Driven Development with Python.” Apple Books.

Additional Tasks in Azure Pipelines

The Azure Pipeline docs are pretty great, including different things to configure based on the programming language you’re using.

For Python projects in Azure Pipelines, there are two tasks to add: 1. Publish Test Results 2. Publish Code Coverage

I have a feeling these will come in handy later.

I’ve only played around with the HTML report code coverage generates a couple of times. Looking at Azure DevOps, it looks kind of cool how they integrate testing into your dashboard.

In your project’s dashboard on Azure DevOps, go to Test Plans -> Runs:

Azure Test Plans / Runs

Choose which test results you want to look at it and there are pretty reports for you to view, and if you keep scrolling down on the right hand side it will give you different outcomes for review. It might save me a few clicks from the manual HTML code coverage builds, so I have that going for me.

Pytest Results

Project Goals

Now it’s finally time to start coding. I have two goals:

  1. Build a web app with the Pyramid web framework the correct way. My first two projects used a module called pyramid_handlers to manage views. This is an outdated way of writing code with Pyramid and I need to learn the modern way.
  2. Use Test Driven Development methodology to finally learn how to write tests using pytest. I’m going to write the test and then the code.

I’m not quite sure how I’m going to do this yet. I think it’s going to be a lot of jumping around a couple different Talk Python training courses. (If you have a rudimentary knowledge of Python, I highly recommend Talk Python’s training courses).

The course Building data-driven web apps with Pyramid and SQLAlchemy contains the knowledge on Pyramid and also includes a chapter on testing. The recently launched #100DaysOfWeb in Python has a bunch of chapters that will be useful in my quest, including Pyramid, Selenium, and Unit Testing Web Apps. That doesn’t even include things I want to eventually learn, like a Javascript introduction, CSS and more. (Yes, I saw the recent Jetbrains survey results and 40%+ of Python developers are using Flask for web development- but I’d like to get good at Pyramid before I switch to a different framework. And I really, really like the Pyramid community. The same could be said about my Python knowledge before diving into Javascript).

So while I’m going to kind of Frankenstein my courses, I’m going to do it using TDD. I’m not going to build a PyPI clone like Building data-driven web apps with Pyramid and SQLAlchemy teaches. Silversaucer.com is just an online playground for me to keep learning, and the first thing I’m going to build is a Randomizer for my vinyl record collection using the Discogs API. I’m going to work through the two courses above and I’ll also be using the following books to learn more about testing:

I’ll keep blogging what I learn and where I get stuck. I need to learn testing, especially if I want to continue maintaining and improving NFLPool and MLBPool2. And learning how to code is fun, in a frustrating kind of way (at times).

Setting up Azure Pipelines - Part 2

In Part 1, I covered the challenges I had in setting up my SSH key with Azure Pipelines to work with my existing Github repository, which contains a new Pyramid project without any customization (yet).

Now that Azure Pipelines could build my project, I spent the last week after that trying to figure out why builds would fail on Azure with Python 3.7, not with Python 3.6 or on my local development machine.

One question I was asked: Why continuous integration if I’m just a hobbyist? I have two answers:

  1. Test-Driven Development with Python by Harry Percival recommends it:

    “Rather than let that happen, we can automate the running of functional tests by setting up a “Continuous Integration” or CI server. That way, in day-to-day development, we can just run the FT that we’re working on at that time, and rely on the CI server to run all the tests automatically and let us know if we’ve broken anything accidentally. The unit tests should stay fast enough that we can keep running them every few seconds.”

    Excerpt From: Harry Percival. “Test-Driven Development with Python.” Apple Books.

  2. It’s cool. And that’s the real reason. Having the little “Azure Pipelines Succeeded” badge on the Github repo page; hooking up the Slack integration to get a message when a build builds or fails; and knowing I’m doing things like a “real” developer might.

But I digress. I set up Azure Pipelines to run two builds - one in Python 3.6 and one in Python 3.7. After I make a commit to the SilverSaucer Github repository, Azure Pipelines automatically starts a job and builds the project.

Azure Builds

Two of the four tests passed.

The good news: The two tests using Python 3.6 pass and it builds!

The bad news: The exact same two tests fail on Python 3.7. (Log)

I made sure my development machine’s version of Python matched Azure’s and upgraded from Python 3.7.1 to 3.7.3 just to make sure - still failed.

I poked at it here and there for a few days and then asked for help in the Pyramid IRC channel. Right away, I received advice to add pysqlite3 and it worked! I used pip freeze to update my requirements.txt file and made sure pysqlite3 was in there, committed, and now I have a shiny badge on my repo.

I still don’t understand why it built on Python 3.6 but not Python 3.7. But it’s working and time to move on.

Coming in part 3: Hooking up Dependabot and the Python 3.7 builds fail again.

Learning pytest using continuous integration with Azure Pipelines (or SSH key hell) - Part 1

Introduction

I’m still on my quest to learn more Python and at the top of that list is learning pytest. I just can’t wrap my head around testing and I know my two Pyramid apps aren’t “complete” until there are tests. (I did write docs, so I have that going for me).

A couple months ago I (very easily) added continuous integration to NFLPool using Microsoft’s Azure Pipelines. I, like many other people, have been blown away by the right turn Microsoft made a few years back to embrace open source, and wanted to give Azure Pipelines a try. Every time I make a commit or Pyup.io submits a pull request to update a Python package, Azure Pipelines builds NFLPool. Of course it fails, because the tests I’ve written so far fail. To end this cycle, I really need to learn how to write tests!

I have a domain I don’t use, silversaucer.com. I have a few different ideas for some projects for the domain. I’m going to build another web app using Pyramid to start, with a goal of properly using classes in Pyramid (and not Pyramid handlers) to start. I’ve also been reading Test-Driven Development with Python, 2nd Edition, that I received from a recent Humble Bundle filled with Python books.

I was thinking that this was a perfect opportunity to learn pytest. I would create a new project in Pyramid and use TDD to write my tests as I write my code. If I’m going to do that, I might as well set up continuous integration right away and pretend I’m a real developer.

Setting up Pyramid

Since I’m going to be using a Test Driven Development philosophy, all I’m going to do is create a Pyramid project and not make any changes to it yet. I used Pyramid’s cookiecutter to create the project and then committed it to my Github repository. That’s it!

Hooking up Azure Pipelines

Here is where the fun starts. I’m not going to go through this process as Microsoft has great documentation for Azure and set up.

Walk through the setup and connect to your repository on Github. Azure Pipelines will create the needed YAML file, commit it and run your first build.

Here is where my build failed for Silver Saucer. I was getting the following error:

Obtaining silversaucer from git+git@github.com:prcutler/silversaucer.git@75932f389536b59993fa780b281170849ff92238#egg=silversaucer (from -r requirements.txt (line 38))
  Cloning git@github.com:prcutler/silversaucer.git (to revision 75932f389536b59993fa780b281170849ff92238) to ./src/silversaucer
  Running command git clone -q git@github.com:prcutler/silversaucer.git /home/vsts/work/1/s/src/silversaucer
  Host key verification failed.
  fatal: Could not read from remote repository.

Here is where I first lost hours over the course of a few days. Azure is pulling my repository using git, not https. I would compare this to my Azure Pipeline for NFLPool, which for some reason pulls my repository using https and works fine. I know a little bit about SSH keys. I’m no expert, but all of my Digital Ocean and servers at home use SSH key authentication to log in and not passwords (yay me for good opsec!) and I have my SSH key on multiple computers without any issues.

Lots of search queries later, I learned how you can change a git repository’s remote URL, but this appears just to be on your local machine. I’m sure I’m missing something simple to make it a global change, but I never figured it out.

Ok, let’s add my SSH key to Azure Pipelines. Again, Microsoft has good developer documentation on how to do this.

  • Step 1: Add your public key to your Azure profile.
  • Step 2: In your projects in Azure Pipelines, go to Pipelines -> Library and choose Secure files. Add your private key (usually id_rsa).
  • Step 3: Add the SSH Task to Azure Pipelines and make sure you authorize the private key - follow Microsoft’s developer documentation for the SSH Task. Update your YAML file:
    # Install SSH Key
        # Install an SSH key prior to a build or release
        - task: InstallSSHKey@0
          inputs:
            hostName: 
            sshPublicKey: 
            #sshPassphrase: # Optional
            sshKeySecureFile: 

The hostname input confused me at first, but here you’re going to go into your ~/.ssh directory and copy and paste the Github entry in your known_hosts file. (This is a hidden directory in your home folder on macOS or Linux. I’m not sure where it is on Windows, sorry!) Paste your public key in sshPublicKey: and the name of your private key that you uploaded in Step 2 above. If your repository is public on Github, you are not going to want to add your sshPassphrase to your YAML file.

My SSH key fails

Again, I lost hours here. I have no idea why, but the SSH key I’ve been using for the last couple of years will not work. I had to create a second SSH key for Azure Pipelines and delete my known_hosts file, clone the repository again (which then updated known_hosts) and paste in the new fingerprint from known_hosts to the YAML file. The new key works fine, but I’ll be damned if I can figure out why my normal key doesn’t work. I’ve compared the fingerprint of my usual key, used it to clone other repositories, but Azure Pipelines refuses to work with it as I would just receive the above error over and over again.

I’m not thrilled with having a second SSH key, and now I have to go to the other two computers I work with and copy it over and add it to my keyring. But it works.

What’s next

Coming up in Part 2: Pytest works in Azure Pipelines in Python 3.6 (but not Python 3.7!)

It was a good week of Python and me

It was a good week for Python and me.

In no particular order:

MLBPool2

I pushed a major re-write of MLBPool2 to production just days before the baseball season ended. Why didn’t I wait until the baseball season was over before pushing a major update? Because MySportsFeeds this summer upgraded their API from 1.x to 2.0 and it included a big change to how they track team standings, especially playoff standings. To give MLBPool2 players better visibility to how they were doing, it was important to get the update out there. There were a few more things pushed in that update which deserve their own write-up.

100 Days of Python

I started the 100 Days of Python training from Talk Python and Pybites. The first couple of days are training on the ``datetimemodule, which just reinforced to me how much more I like Pendulum. I haven’t been updating the daily log (or tweeting), but I have been working on Python daily.

Pyramid Documentation

I mentioned in my last blog post a few weeks ago how I’ve been learning about testing. One of the things I noticed when reading up on Pyramid and pytest was that the Pyramid documentation refers to pytest as py.test in its documentation, which is an old way of doing it.

GitHub and partners started Hacktoberfest this week, so it was a perfect time to go through all of the Pyramid documentation and update it. So I did, and it was a good learning opportunity. It’s not as simple as an easy search and replace - reading through the docs to make sure the changes are done right also teaches me about the features, and learning about pytest and especially coverage was great. Talking to the developers, Pyramid has a 1.10 release coming very soon, so I got those changes in and then dived into another discussion topic on IRC, cookiecutters.

With the upcoming 1.10 release, Pyramid will be merging three of it’s cookiecutters into one. Even though we’ll update the documentation, including the README, there are probably some developers who have a workflow to just pull the cookiecutter from the command line. I added a message to the two deprecated cookiecutters that they are deprecated and to use the correct one. I did spend some time going through the Talk Python course on cookiecutters, which was good, too. I was looking for a way to update the pre-commit hook to add the deprecation message, but no luck on that.

And speaking of Hacktoberfest, I met the goals! (Not that I’m done yet).

It was a good week.

Hacktoberfest 2018

Learning How to Test in Python and Pyramid

tl;dr: I don’t get testing, but thanks to Talk Python I’m starting to put it together. If you’ve taken both the Python for Entrepreneurs course and the Building Data Driven Web Apps with Pyramid this might help you write tests using the Python for Entrepreneurs code. And if you’ve taken courses at Talk Python, take advantage of the office hours!

I don’t get testing in Python at all. I don’t have a computer science degree and Python is a hobby for me. I’ve learned enough Python to build two apps with Pyramid, but I didn’t write any tests. (Shame on me!) I was lucky enough to be asked to review Brian Okken’s Python Testing with pytest book last year, and after reading the first few chapters, it was way over my head and I had to apologetically let him know I just didn’t have the knowledge to review it. I’ve read dozens of articles on testing, but nothing I’ve come across explains what to test, much less how to start it.

I’ve already raved about the training courses from Talk Python and I finally bought the Everything Bundle this summer. So when Michael Kennedy launched his latest course, Building data-driven web apps with Pyramid and SQLAlchemy, which includes a chapter on testing, I was pumped. (That, and how to build a Pyramid app using classes and not the pyramid_handlers module, but that’s a different blog post for a different time).

I decided to jump around a bit in the training and dive into the tests portion of it. What worked: Things started to click. The training and his code showed how to write some tests to test the views and routes within a web application. I learn best by seeing the code examples and following along with the training, so this was exciting. Learning the “3 A’s of Testing (Arrange, Act and then Assert) was big - not once in any of the articles I’ve read on the web did I come across those terms.

What didn’t work: Using the code examples to test against my two existing Pyramid applications (NFLPool and MLBPool2). The first reason is because I’m using the pyramid_handlers module (which is now not supported) and this latest course does not use this package. The second reason is that one of the tests you write tests the whole web application - but as part of the Python for Entrepreneurs course, I had enabled other functionality, such as sending emails, Rollbar for errors, and a few other things. The Building Data Driven Web Apps course mentions that you might have to pass some variables to the test to get it to work, but I didn’t have any luck.

But! But! If you’ve purchased training courses from Mr. Kennedy, he offers office hours if you need help with the training courses. These are not consultation sessions, your questions have to be related to the course, and he was kind enough to probably bend the rules slightly to help me get up and running tests with my existing applications. After spending a few hours trying to write the tests over the weekend, I was lucky that office hours were this past Monday over my lunch hour. He publishes the code examples on Github, so I don’t think there’s a problem with me sharing some of these code snippets. (And by blogging about this, I’m hoping to help commit it to memory as well, as well as help anyone who has also taken both courses).

Testing Registration

Let’s look at testing the Account methods, from the Github repo from the course. If you click the link, you’ll see that there are three tests:

  • Test that registering an account works
  • Test that an account already exists
  • Test that a user didn’t submit their email address when registering

Since I was having problems, I was just trying to do one at a time, starting with the last one. Here you’re going to test that an email address was not entered as part of creating an account:

import unittest
import unittest.mock
import pyramid.testing


class AccountControllerTests(unittest.TestCase):

    def test_register_validation_no_email(self):
        # Arrange
        from pypi.viewmodels.account.register_viewmodel import RegisterViewModel
        request = pyramid.testing.DummyRequest()
        request.POST = {
            'email': '',
            'password': 'a'
        }
        # noinspection PyTypeChecker
        vm = RegisterViewModel(request)

        # Act
        vm.validate()

        # Assert:
        self.assertIsNotNone(vm.error)
        self.assertTrue('email' in vm.error)

Using unitest, we’ll import the RegisterViewModel, which defines what’s needed for an account - a name, an email address, and a password. Pyramid has some testing built into it, so you call the pyramid.testing.DummyRequest() and pass it a POST that includes a password, but no email. You then pass that request to the RegisterViewModel which has a method to validate the information is correct:

    def validate(self):
        if not self.email:
            self.error = 'You must specify an email address.'
        elif not self.name:
            self.error = 'You must specify your name.'
        elif not self.password:
            self.error = 'You must specify a password.'
        elif user_service.find_user_by_email(self.email):
            self.error = "This user already exists!"

But when I ran that test on NFLPool, I kept receiving an error: TypeError: __init__() takes 1 positional argument but 2 were given'

Let’s look at what Michael showed me that does work:

import unittest
import unittest.mock
import pyramid.testing


class AccountControllerTests(unittest.TestCase):

    def test_register_validation_no_email(self):
        # Arrange
        from nflpool.viewmodels.register_viewmodel import RegisterViewModel

        data = {
            'first_name': 'Paul',
            'last_name': 'Cutler',
            'email': '',
            'password': 'Aa123456@',
            'confirm_password': 'Aa123456@'
        }
        # noinspection PyTypeChecker
        vm = RegisterViewModel()
        vm.from_dict(data)

        # Act
        vm.validate()

        # Assert:
        self.assertIsNotNone(vm.error)
        self.assertTrue('email' in vm.error)

If you’ve taken the Python for Entrepreneurs course, you need to do it slightly different. The NFLPool app requires a couple more fields for registration, which is what you see in the data dictionary created in the test above. You then pass that data dictionary to the view model using vm.from_dict(data) instead, and the run the validate method.

And look what you get! Ran 1 test in 0.566s

OK
0

Process finished with exit code 0

It worked! I still have some work to do with one of the three included tests, as I require my users to create a strong password, so I need to re-write my test to account for that, but the other two tests worked for me.

One last note for the other two tests - the Python for Entrepreneurs course uses a class in the AccountService and Data Driven course does not. You need to account for this when assigning a target for the test.

From the Data Driven course:

`target = 'pypi.services.user_service.find_user_by_email'

For Python for Entrepreneurs it looks like this:

`target = 'nflpool.services.account_service.AccountService.find_account_by_email'

Testing the Pyramid App

Testing the entire Pyramid App turned out a bit trickier and I owe Michael a debt of gratitude as I would have never figured this out. The Python for Entrepreneurs class has you include Rollbar (for error reporting) as well as setting up logging. I did get the below test to run, but I had to comment out these things in NFLPool’s __init__.py which kind of defeats the purpose of testing. (But hey, I was learning and it was good to validate I could get a test to run).

From the Data Driven web course, to test the app it looks like:

import unittest
import pyramid.testing


class HomeControllerTests(unittest.TestCase):

    def setUp(self):
        from pypi import main
        app = main({})
        # noinspection PyPackageRequirements
        from webtest import TestApp
        self.app = TestApp(app)

    def test_home_page(self):
        # noinspection PyPackageRequirements
        import webtest.response
        response: webtest.response.TestResponse = self.app.get('/', status=200)

self.assertTrue(b'Find, install and publish Python packages' in response.body)

But if you’ve built an app using Python for Entrepreneurs, you need to do a couple things.

First, create a web_settings.py file and add:

import os
import configparser

config = configparser.ConfigParser()
folder = os.path.dirname(__file__)
file = os.path.abspath(os.path.join(folder, '..', '..', 'development.ini'))

config.read(file)
main = config['app:nflpool']
rollbar = config['rollbar:test_settings']

settings = {**main, **rollbar}

Make sure to change the line main = config['app:nflpool'] so the app name equals your app’s name.

Open your Pyramid dunder init file and update your logging configuration with an if / else statement:

def init_logging(config):
    settings = config.get_settings()
    if settings.get('logging') == 'OFF':
        log_filename = None
    else:
        log_filename = settings.get('log_filename', '')
    log_level = settings.get('log_level', 'ERROR')

    LogService.global_init(log_level, log_filename)

    log_package_versions()

And add the if / else statement. You’ve now accounted for both Rollbar and you won’t create a log in your tests directory when running this test.

Lastly, you need to update your development.ini file to account for the Rollbar changes. Comment out the rollbar.root below so you don’t receive notifications for errors in development and add the [rollbar:test_settings] block below.

#  Rollbar settings

rollbar.access_token = YOUR_TOKEN
rollbar.environment = dev
rollbar.branch = master
# rollbar.root = %(here)s

[rollbar:test_settings]

rollbar_js_id = NONE
rollbar.access_token = NONE
rollbar.environment = dev
rollbar.branch = master
rollbar.root = blank

That’s it! Hopefully I haven’t missed anything. I’m excited to start writing tests for my two apps and I have a lot to write. Once I get my head wrapped around these tests, the next phase will be to see if I can migrate some of them to pytest - but one thing at a time. (And then finally review Mr. Okken’s book, too!)