Bugs in Pygrr? The Debug class
So, we're almost done with the refactoring! Let's make a whole new class...
This post is about Pygrr's Debug class, which has turned out to be the biggest class yet! At the time of writing, the class has a whopping 22 files and 9 sub-folders inside it...
Also, fun fact: Pygrr currently has 4358 lines of code in all of its files - that's a lot! I worked this out by writing a program that walks Pygrr's directory, counting each line in every .py file.
Now, onto the blog post...
Currently, if the end-user inputs invalid data or tries to do something with Pygrr that causes an error, they'll just get a bunch of back-end errors, which won't mean much to them, and then they won't know the cause of the error, or, consequently, how to fix it. In this entry, I'll put an end to that!
So, let's create a new package in Pygrr, called Debug. This will contain our new custom errors for Pygrr!
Now, what is an error, I hear you ask. Well, an error is simply a type of exception (a problem with the program you've written), that isn't handled, and therefore closes the program. Raising an error (calling it), force stops the running of the program. Another type of exception is known as a warning. This basically still tells the user what went wrong, but isn't a fatal exception - so the warning is internally handled, and the program keeps running!
So, we've got that over with, let's also make a custom warning method. This does what it says on the tin - raises a warning that is handled. Now let's make our error base class. A base class is basically a class which other classes can extend from, inheriting the properties from the base class without you having to code them again! Let's call it PygrrError. All of Pygrr's custom errors will extend from this.
Beep boop, boop beep... (this line is here to show that a lot of coding went on in-between the last paragraph and the next paragraph)
And, cool beans, we have several shiny new errors now! These will be raised when the user does something wrong that causes a fatal problem - so now the user knows what went wrong! Here are all the errors so far, and a brief description of them:
InvalidDataTypeError: Called whenever an unexpected data type appears.
NonExistantPropertyError: Called whenever a property that doesn't exist is called.
InvalidPointError: Called whenever there is an error in creating / setting a Pygrr Point.
InvalidSetError: Called when an error occurs validating a set of points. A set of points is either a list of tuples with 2 items, a list of pygrr.Point objects, or a list of lists with 2 items
InvalidModelFileError: Called when an invalid model file is opened.
InvalidModelTypeError: Called when an invalid type of model is given.
Very nice! I also have a neat idea to have an error code in each error, which corresponds to a more detailed description and examples in the documentation of Pygrr, when we get round to that!
Now, let's cover warnings first. To do this, we need some context on privacy of variables and classes in Python. As it is dynamically typed, private variables don't really exist, which means that anyone can access an object's data and modify it directly, instead of using functions which correctly format and validate the data. This is obviously a problem. So, we're going to code a custom private class, which is the base class for private objects.
Beep boop, boop beep... (this line is here to show that a lot of coding went on in-between the last paragraph and the next paragraph)
When you extend from this class, and want to have public variables that the user IS allowed to save, you must create a property out of those - a property will be recognised by the base class, and it will allow the user to modify it.
It comes with custom developer commands which allow the developer to modify the object's private properties without raising an exception! Note: as full privacy doesn't exist in Python, it is possible for a determined user to call these functions to interact with the data, but it is a lot harder.
If a user tries to interact with a private object's data, they will raise these warnings, which are handled. Note that these will not be raised when a public property is accessed.
PrivatePropertyCreationWarning: Called whenever a property has attempted to be created in a private class.
PrivatePropertySetWarning: Called whenever a private property has been attempted to be set.
PrivatePropertyGetWarning: Called whenever a private property has attempted to be read.
PrivatePropertyDeletionWarning: Called whenever a property has attempted to be deleted in a private class.
Not only are these warnings and errors outputted into the console, they should also be outputted into the Pygrr Debug Stream. What on earth is that? Basically, whenever a file is run that imports Pygrr, a folder named "Debug Stream" appears next to the file, which contains some text files.
They will find text files like this:
Inside these files are the written output of the stream, so, warnings, and errors! They allow the developer to read the problems long after the program has been closed.
latest.txt stores the most recent run - this is the only file that will actually appear by default with Pygrr, unless you go into the config file and change DebugStreamLevel to ALL. It can also be LATEST or NONE. Awesome stuff!
The other files correspond to a unique runtime, and will not be overwritten - but they do rack up quite a few of them, which is why it is by default latest only!
Now, there's a few tiny more things we need to add. Two of these things are done - the possibility for the user to create their own custom errors that will be saved, and their own warnings that will be saved. The third and final thing is pygrr.Debug.Log(message). This will save the message to the Debug Stream - but not as a warning or an error, just a message!
Isaac, over and out...