Musings on Versioning and Poetry

Musings on Versioning and Poetry

Well, it’s almost the end of February, so I thought I’d say hi. There really hasn’t been much that has piqued my interest in the coding realm recently to want to jump in and write about it. However, the other day I came across something where I had to dig into python versioning with poetry. I thought you all would love to hear my thoughts on it. Because why else would you be here? Anyhoo, poetry.

So you know how a project has packages you’re going to use for it and those are your dependencies. And those packages have dependencies and keeping it all straight manually is a thing. So you use poetry to keep track of it all. You have a pyproject.toml file you can define your packages and the versions you need them to be. And then you’ve got your poetry.lock file that keeps track of all the packages and versions and tasty nuggets you have in your project.

My questions were:

  1. Can my poetry.lock file update itself by running poetry update without me having updated my package versions in pyproject.toml?
  2. When do i use poetry install versus poetry update?

What I discovered:

So if you update poetry without updating your pyproject.toml file, it is possible for poetry.lock to update itself. Here is why. It depends on what versions you have defined. So say you have version ^1.2.3 defined for a package and the package version you’re running is 1.2.3. If that package comes out with another patched version, say 1.2.5, your pipfile.lock would update because it falls into the requirements you defined (versions >=1.2.3 and <2.0.0)

The next question is answered in their documentation (shocker, I know. Who would have thought reading the documentation would yield results?) Anyhoo, poetry install installs the versions you have defined in your poetry.lock file and doesn’t do any resolving. Whereas, poetry update resolves the latest dependencies and updates the poetry.lock file. So it’s like running poetry lock (locks your dependencies from pyproject.toml) and then poetry install but doing it with one command.

Overall an interesting topic. Do you use poetry for package dependencies?

More next time!

Rachel

Python Testing: A Philosophy

Python Testing: A Philosophy

It’s been a bit since I’ve been here, but there really haven’t been many updates in regards to new and exciting topics in the working realm for me. One thing that I have wanted to cover is Python Testing. Not necessarily how to do it, but the motivation behind writing good tests. Am I an expert in test writing? No. Have I been learning what makes one test better than another? Yes (hopefully). Here’s a few thoughts I have regarding testing.

Be very explicit with naming your test function. When your tests run you’ll see the names and if they’re all called testX, that’s not super helpful. Try something like test_blue_cat_food_bowl_half_filled_hungry_cat. Or whatever. Even if you don’t love the name I made, you know you’re testing how the blue cat food does with a hungry cat and a half full bowl. Use docstrings to then describe in more detail what data you’re setting up for the test and what you’re calculating and expected results.

# not using decorators or anything here for this illustration
def test_blue_cat_food_bowl_half_filled_hungry_cat():
    """
    Tests that the cat will eat the cat food in the bowl.
    One bowl, blue food, one cat, cat's hunger level is hungry
    """

Have your actual testing test only one idea at a time. Say you also need to test to make sure you have the right breed of cat. Make a separate test for that. Sure you could go ahead and test for it in this test, but that’s not within the scope of your function name. Plus if that part of your code breaks in regards to the cat breed, you’re going to have to update more than just that one test that deals with checking it.

Ensure you’re data is set up correctly. Your test may pass, but you may accidentally have something in your test assuming that you’re always being given a full cat because that’s the default cat setting in your factory or whatever. Make sure that anything you’re testing, you explicitly define the test values so you’re not getting false positives.

Test the small things first and then work up to more complex testing. You may want to just set up your one test that is supposed to test everything is summed and converted correctly and just figure that it will pick up the small things. I’ve found this is not the best way to start off. Writing lots of smaller, specific tests to test out all the smaller building blocks of the larger bits of code that use those blocks has proved to be way more effective. It’s also a plus that if you know all your simple cases work and a more complex test fails, then you can narrow down what is going wrong a bit easier too. Which also goes back to my earlier point that if something breaks then you are able to fix that little bit way easier than in your mega test.

Those are the big things that I have learned are super important with setting up and writing sensible tests. It’s pretty easy to find out how to write a test syntactically, but unless I’m just lazy with my Google searching, it’s a bit more difficult to find good references on what makes a good test. Anyhoo, let me know if you have any other testing wisdom!

-Rachel

You Should Be Using Python Classes

You Should Be Using Python Classes

Python is cool. So are Python classes. If you develop with Python and haven’t used them yet, I suggest that you do. They’re something that I started using a few years ago and they have come in so handy when it comes to re-using code and maintaining scope easily.

When I first started learning about and using classes there were a few things that confused me and were different than just writing a simple script. You can’t -just- run them from the command line and expect them to work. You have to instantiate your class. That means you have to call it and it will create an empty object for you. So say you created a python class in zippy.py.

# zippy.py
class CoolClass:
    def hi(self):
        return 'Hi!'

# instantiate the class from the same file
coolClassObject = CoolClass()

The first thing is that your function is now inside a class. The second thing is you have this variable thing called self. So to instantiate this class you can call it within the same file or you can pull up a python shell and import it.

# pretend this is a python shell

import zippy
coolClassObject = zippy.CoolClass()

So now you’ve got a variable with your class object in it. Yay! Now I guess you want to call your hi function. Super straightforward. coolClassObject.hi() and there you go, you’ll get ‘Hi!’ returned.

The coolest thing about classes (In my opinion, but this is my blog, so what else would you expect?) is the __init__(): method. It’s basically a place where you can initialize all your variables, data passed in when you instantiate the class, etc. You can then use everything in your __init__(): throughout your class. I like being able to define static, global values or any data structures; if I have URLs that I need to reference for APIs I’ll also define them there. You can also pass in parameters when you first instantiate the class (required or optional). So so handy.

# zippy.py
class CoolClass:
    def __init__(yourName='Bort'):
        self.name = yourName
        self.group = 'Cool'
    def hi(self):
        msg = 'Hi, {0}. You are in the {1} group'.format(self.name, self.group)
        return msg

# instantiate the class from the same file with no parameter passed in
coolClassObjectDefault = CoolClass()

# instantiate the class from the same file with your name
coolClassObjectRachel = CoolClass('Rachel')

I’ve added the init method now and I’ve defined an optional variable that you can define when you first instantiate the class. If you’ve made changes in a python shell and you’re using python3 (which I’ll be using unless I say otherwise), you’ll need to import imp to reload the module.

When you run it now, you’ll get your message to return with the default argument of Bort for the first object and Rachel will be in the second one. Notice how I could instantiate two versions of the same class? Super handy.

I’ll wrap up with a brief talk about self. I like to think of it as mega-variable. Python officially calls it an instance variable. Anything that got defined in __init__(): is stored in self and you can then use it throughout the rest of the functions in your class. You just have to make sure that self is passed into each function (you’ll get an error otherwise) and then you can use it throughout your class functions.

-Rachel