Anatomy of an Open Source Python Project

From Idea to World Domination

Chris Want

Roadmap

  • My background
  • Discuss a hobby project I created
  • Explain why I made it open source
  • Describe the tools I used to ensure that the project is sustainable
  • Examine measurements of success
  • Reflect on what I would do different next time

Me

  • Mathematics background
  • Former scientific visualization analyst with the University of Alberta
  • Former Blender developer

I set up the first 3D printing service bureau on campus (in Edmonton??) circa 2002

I continue to be a 3D printing hobbyist, working on a variety of projects

What does my typical 3D printing workflow look like?

  • I modify geometry in Blender until my model looks good
  • I export my model to the STL format
  • I upload my STL to a 3D printing service bureau, select the material, and add to cart

I've been a Python programmer since 1999

My team recently started to teach Python workshops, with a focus on Jupyter notebooks

Getting started with Python?

  • I recommend checking out Jupyter Notebooks
  • A really easy way to get started is with the Google Colaboratory
  • Google for "colab"

The itch ...

I wanted to use Blender to make a Klein bottle tiled with hexagons (a la Bathsheba Grossman)

Nailed it!

An Idea

Why not use ... math? (and Python)

Easy, to do if you don't want hexagons.

BUT ... BUT I WANT HEXAGONS!!!

Can't find a tool that does this, so I'll have to write my own software to make them.

Requirements

Produce a 3D, topologically correct (air-tight) mesh, tiled with hexagons

  • Input a math formula for the surface (a function in python that has two args u,v that returns a point in 3D space)
  • Input u_num, v_num parameters to control the number of hexagons in each direction
  • Handle cyclic domains (objects that wrap, like cylinders, torii, Klein bottles, etc.)

Design

The code has two classes 'Tile' and 'Tessagon' (generates the tiles)

Proof of Concept

It works, and I've created something useful -- now what do I do with it?

Why Open Source?

  • It's important to give back
  • Makes it easy to install on any future machines I might own
  • Reputational credit has more value than the amount of money I would likely make with the script (I've earned $0 at my Shapeways shop since creating it in 2013)

Tessagon on Github

Initial commit is one python file that has both classes, and a demo blender file.

DONE!

Or am I ... ?

Scope creep

If I can do this for hexagons, surely I can do this for a triangle tiling?

OOP Refactor

Parent classes: Tile, Tessagon

Subclasses:

HexTile, HexTessagon TriTile, TriTessagon

But I can't stop ...

25-ish subclasses added ...

Color Patterns

Rotation of Tilings

Automation of documentation

A script that runs in Blender generates screenshots/thumbs/markdown

Hexagonal Architecture

This is all just math, so why tie it to Blender?

Shimmed a generic output 'Adaptor' to support Blender, VTK, and Python lists/dict data structures.


               def add_vert(self, index_keys, ratio_u, ratio_v, **kwargs):
                   # Use the mesh adaptor to create a vertex.
                   # In reality, multiple vertices may get defined if symmetry is declared
                   vert = self._get_vert(index_keys)
                   if not vert:
                       coords = self.f(*self.blend(ratio_u, ratio_v))
                       vert = self.mesh_adaptor.create_vert(coords)
                   ...
            

Metadata and Discovery

I created a layer so that different tiling types can be easily found.

The immediate goal was to create a nicely-ordered, self-generating menu of tilings in Blender.



               find_all = TessagonDiscovery()
               find_all.to_list() # a list of all of the tessagons

               regular = find_all.with_classification('regular')
               regular.to_list() # The three regular tilings (HexTessagon, SquareTessagon, TriTessagon)

               regular.inverse().to_list() # All tilings except for the regular ones
            

I also started making stuff

Sustainability

I wanted this project to be set up to have street cred, and I wanted to ensure that it was successful in the long term

Branch structure

Pull requests of feature branches merged into `development` branch as squashed rebases

`master` branch for releases only, merging in `development` when the time feels right

Style: PEP8 & Linting

https://www.python.org/dev/peps/pep-0008/

It's bad enough having to read other people's code, let alone having to read code that looks like it was written by three different guys

Linting: Flake8


              pip3 install flake8
            

Stackoverflow: "Linting is the process of running a program that will analyse code for potential errors."

Flake8 looks for violations of PEP8

Before ...


$ flake8 pythagorean_tessagon.py
pythagorean_tessagon.py:22:1: E302 expected 2 blank lines, found 1
pythagorean_tessagon.py:23:3: E111 indentation is not a multiple of four
pythagorean_tessagon.py:25:13: E201 whitespace after '{'
...
            

... after


$ flake8 pythagorean_tessagon.py
            

(no output == flake8 isn't angry)

Can turn off specific tests inline


import os
import sys

this_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.append(this_dir + '/../..')

from tessagon.core.tile import Tile  # noqa: E402
            

E402 corresponds to: module level import not at top of file

A foolish consistency is the hobgoblin of little minds ...
Ralph Waldo Emerson

Test suite

Ensures the software operates correctly

When done right, become your "license to make changes"

pytest


              pip3 install pytest
              # Measures test coverage
              pip3 install pytest-cov
            
  • Tests go in the `tests` directory
  • Class names begin with `Test'
  • Methods begin with `test_`

Only 25 tests (yikes)


class TestAbstractTile(CoreTestsBase):
    def test_u_range_v_range_params(self):
        tessagon = FakeTessagon()
        tile = AbstractTile(tessagon, u_range=[0.5, 1.0],
                                      v_range=[2.5, 4.0])
        assert (tile.corners == [[0.5, 2.5],
                                 [1, 2.5],
                                 [0.5, 4.0],
                                 [1.0, 4.0]])
            

Coverage


$ pytest --cov=tessagon tests/
[SNIP]
Name                                  Stmts   Miss  Cover
---------------------------------------------------------
tessagon/__init__.py                      0      0   100%
tessagon/adaptors/__init__.py             0      0   100%
tessagon/adaptors/blender_adaptor.py     18     18     0%
tessagon/adaptors/list_adaptor.py        23      0   100%
[SNIP]
tessagon/types/zig_zag_tessagon.py       58     46    21%
---------------------------------------------------------
TOTAL                                  2010   1276    37%
            

(... more yikes)

Continuous Integration

Ensure that code coming into the project is able to merge and passes project standards

For tessagon, we run the test suite and the linting

Travis CI

https://travis-ci.org/

  • Free account
  • Integrates with Github

Github project setup

.travis.yml


              os: linux
              sudo: false
              language: python

              matrix:
                include:
                  - python: 3.6
                  - python: 3.5
              install:
                - pip install flake8
              script:
                - pytest
                - flake8 --verbose
            

Pull request

Builds

Packaging & Distribution

https://packaging.python.org/tutorials/distributing-packages/

Re-structuring


.                         .                  
├── adaptors              ├── demo           
├── core                  ├── documentation  
├── demo                  │   └── images     
├── documentation   ==>   ├── tessagon       
│   └── images            │   ├── adaptors   
├── misc                  │   ├── core       
└── tests                 │   ├── misc       
    └── core              │   └── types        
                          └── tests          
                              └── core
            

setup.py


              from setuptools import setup, find_packages

              # See next slide for long_description

              setup(
                  name='tessagon',
                  version='0.4.2',
                  description='Tessellate your favorite 2D manifolds with triangles, ' +
                  'hexagons, and other interesting patterns.',
                  long_description=long_description,
                  url='https://github.com/cwant/tessagon',
                  author='Chris Want',
                  classifiers=['Development Status :: 3 - Alpha',
                               'Intended Audience :: Developers',
                               'Intended Audience :: Manufacturing',
                               'Intended Audience :: Science/Research',
                               'License :: OSI Approved :: Apache Software License',
                               'Natural Language :: English',
                               'Programming Language :: Python :: 3 :: Only',
                               'Topic :: Artistic Software',
                               'Topic :: Multimedia :: Graphics :: 3D Modeling',
                               'Topic :: Scientific/Engineering :: Mathematics',
                               'Topic :: Scientific/Engineering :: Visualization'],
                  keywords='tesselation tiling modeling blender vtk',
                  packages=find_packages(exclude=['tests', 'demo', 'wire_skin.py']),
                  python_requires='~=3.5'
              )
                            
            

Note: documentation is in ReStructedText (RST), not Markdown


              long_description = '''
              ===========================================
              tessagon: tessellation / tiling with python
              ===========================================

              Tessellate your favorite 3D surfaces (technically, 2D manifolds) with
              triangles, hexagons, or a number of other curated tiling types!

              Please visit the Github repository for documentation:

              ``_

              Either checkout the code from the Github project, or install via pip::

                  python3 -m pip install tessagon

              or::

                  pip3 install tessagon

              '''[1:-1]
            

Build packages

Source distribution


              python setup.py sdist
            

Universal wheel (build package):


               python setup.py bdist_wheel --universal
            

              $ ls dist/
              tessagon-0.4.2-py3-none-any.whl  tessagon-0.4.2.tar.gz
            

Uploading to PYPI

We want to host the package on the "Python Package Index" repository

Get an account on both regular and test site

https://pypi.org

https://test.pypi.org

Twine uploads the project to PYPI


               pip3 install twine
            

~/.pypirc


                [distutils]
                index-servers=
                    pypi
                    testpypi

                [pypi]
                username = cwant

                [testpypi]
                repository = https://test.pypi.org/legacy/
                username = cwant
            

twine

Upload to test pypi ...


                twine upload --repository testpypi dist/*
            

(Password required)

twine

... or to real pypi


                twine upload dist/*
            

(Password required)

https://pypi.org/project/tessagon


               pip3 install tessagon
            

How do we measure world domination?

Stars and Forks

Stars from:

  • Canada
  • US (Microsoft Research)
  • Italy
  • Germany
  • New Zealand
  • Australia

SEO

Number one hit searching for "tessagon" on Google (beating out somebody named Tessa Gon).

Four of the page one hits are about this project, most of the top image hits are about this project

Terms like "python tiling", "python tessellation" fair pretty poorly

Number three hit for "python 3d tiling"

Only one developer still, so not really dominating on that front

Conclusion

By carefully paying attention only to the statistics I like (and ignoring the ones I don't) I can totally say I achieved world domination

Lessons Learned

Should have started the test suite and following PEP8 earlier

Should have been following proper packaging structure earlier

More care could have been taken earlier to reduce churn on binary files

Thanks for listening!

Questions?

This presentation
https://cwant.github.io/python-os-presentation
Slides source
https://github.com/cwant/python-os-presentation
Tessagon
https://github.com/cwant/tessagon
https://pypi.org/project/tessagon