Self updatable Python application package – codename: PUPPA
P.UP.P.A
is a friendly boilerplate for a python package (made by zipping the project files) able to update itself via command line.
The features of PUPPA are easy explained by looking at the file tree of the project:
Requirements
Both requirements are optional. You can compile your Python package if you want to deliver it easily on Windows. You can also use a different source for your versioning, i just used Github because it was easy to show different versioning there and to understand the logic of the project using tags/refs and viewing the commit history.
Technical details
Starting from the root folder of the project hosted here
root folder
|_____ __main__.py is the entry point of the project. Necessary when you package the application.
|_____ README.md (optional) contains the documentation (optional)
|_____ dist/ is the folder that contains the zipped files that I will defined as distributions
|_____ lib/ is the folder that contains the logic and the dependency of our application
dist/ folder
This folder contains the distribution of our software. In order to create a puppa
distribution you can simply zip and rename the files and folders above (exclude dist/). This can be simply achieved on linux/mac with a single command:
zip -r puppa.zip __main__.py README.md lib/ && mv puppa.zip puppa
Now you should have your extension-less puppa file, ready to be committed on Github (or put online where you want).
Note: I used Github exactly because it generates for each refs (tags in my example) a different tree. This way i can keep track of my distributions history easily, but the same can be achieved making an ftp folder structure like http://example.com/puppa/dist/{version}/puppa
and then modifying the update.py
script.
lib/ folder
|_____ manager.py the main script that handles the inputs
|_____ purge.py the purge() function script
|_____ update.py the update() function script
|_____ download/ download dependency: required to download files
|_____ colorama/ colorama dependency: required to make nice cross platform cli outputs
download/ and colorama/ are two dependency. Every project can have multiple external dependency and you can include them here in the lib folder (or elsewhere if you prefer) in order to be shipped with the dist.
manager.py file
import sys, colorama, others..
from update import *
from purge import *
class manager:
def __init__(self, arguments):
## manager config
self.__variableA__ = ...
self.__variableB__ = ...
self.__variableC__ = ...
## manager logic
# python puppa
if len(arguments) < 2:
print self.__doc__
return None
command = arguments[1]
# python puppa [options] [arguments]
if command == 'update':
# python puppa update
update(self,arguments)
elif command == 'purge':
# python puppa purge
purge(self)
elif command == '.. other minor commands ..'
def getCurrentEntryPath(self):
return os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)),'..'))
This file includes the program configuration without too many sugar and handles the commands. If you have called python puppa update
then you will invoke the update()
function from the update.py
passing other arguments if there are, if you have called python puppa purge
you will invoke the purge()
function from purge.py
.
Moreover we have a manager.getCurrentEntryPath(self)
method that returns the absolute path of the puppa
file.
update.py file
import os, download, etc ....
class update:
def __init__(self, manager, arguments):
# location of the package on the remote repository
remotepuppaloc = urlparse.urljoin(manager.__puppagitlocation__, manager.__filename__)
# get the tag branch or master
version = 'master'
### optional versioning based on Github - remove if not required -
if len(arguments) > 2:
..check if the version file exist on github and if true
..change version to arguments[2], ex: the 0.2 in python puppa update 0.2
### end of optional versioning
# get the url of the package
remotepupparepo = manager.__repositoryurl__ + '/blob/'+version+'/'
url = urlparse.urljoin(remotepupparepo,remotepuppaloc)+'?raw=true'
# download
download(url,manager.__filename__+'.dist')
# replace
os.remove(manager.getCurrentEntryPath())
shutil.move(manager.__filename__+'.dist',manager.getCurrentEntryPath())
The update script simply retrieve the distribution url from the information coming from the manager, then it downloads the distribution with a different and temporary filename puppa.dist
, then it removes the actual puppa
file and it renames the puppa.dist
in puppa
.
purge.py file
import os, colorama, etc...
class purge:
def __init__(self, manager):
os.remove(manager.getCurrentEntryPath())
Quite self explanatory: it removes the current puppa
file. Useless function? Maybe! But you can see it as a skeleton for an uninstall feature.
Conclusions
Nothing is worth more than a screenshot:
I simply:
- Downloaded the 0.1 version of the puppa from github
- Verified the version with
python puppa -v
- Update it to the latest version with
python puppa update
- Downgraded it to the specified 0.2 version with
python puppa update 0.2
- Uninstalled the file with
python puppa purge
This package stops here.
It’s just a POC and experiment that i made without too much interest in features and optimization.
I wanted to experiment a familiar versioning workflow (npm) in a different environment (a zip packaged python application).
I hope this can become useful to somebody!
Happy puppa to everybody!
No comments yet.