Package: easycheck
Theeasycheckpackage offers a lightweight tool for running functionized checks within Python code; it also offers functions to be used in testing - particularly in doctests, but also in pytests, for which purpose some of the functions have dedicated aliases (starting off withassert_instead ofcheck_). You can also switch off alleasycheckchecks, by setting the"EASYCHECK_RUN"environmental variable to"0".The idea is to use theeasycheckfunctions to check conditions that are _not_ assertions. The checks work in the following general way: When a condition is met, nothing happens (in fact, the function returnsNone); if it is violated, an exception is raised or a warning is issued. The main differences betweeneasycheckfunctions and assertions are as follows:Assertions are meant to be used conditions that _must_ be true (when only the code is correct). So, if an assertion is incorrect, it means something is wrong with the code. You should never use assertions to handle regular exceptions, like those related to data or arguments.Unlike assertions,easycheckfunctions are to be used to check conditions related to things like data and argument values, and to handle regular exceptions.While assertions only raiseAssertionError, you can choose any exception to be raised by easycheck functions.When usingeasycheck, instead of raising an exception, you can issue a warning.The maineasycheckfunctions (with names starting off withcheck_) are designed in such a way that they can be used as easy-to-understand code that checks whether one or more conditions are met. They can be used instead ofif-blocks, which are normally used to check conditions and raise exceptions (or issue warnings) if they are not met. So, you can do the following:ifnotisinstance(x,(float,int)):raiseTypeError('x must be a number')ifx>10:raiseValueError('Too high value of x')or you can useeasycheckfor this:check_type(x,(float,int),message='x must be a number')check_if(x<=10,ValueError,'Too high value of x')Theeasycheckapproach has two main advantages over this classical approach:It saves a little space; not much, since most often you’ll end up with one line of code instead of two, but not always, particularly when you provide an exception type to be raised and a long message.Mainly, it increases code simplicity and readability, since both the names of easycheck functions and their arguments are designed in such a way that the reader immediately understands what is being checked.You can also issue a warning:check_if(x<=10,Warning,'For stable functioning of the function, ''x should not be greater than 10.')The package also offers functions dedicated to testing, e.g.,assert_type(x,(float,int))assert_if(x<=10)Themessageargument has the default value ofNone, which does the following. If the exception class provided inhandle_withis built-in (that is, can be found indir(builtins)), no message is provided. But if it is not a built-in exception (or warning) class, then the exception/warning class’s docstring is taken as the message. This is a convenient way of providing a  typical message. If you want to customize the message (e.g., depending on the value of a variable), you should use a customized string (e.g., through an f-string). But if you do not want to use any message with a custom exception/warning, simply provide an empty string (message='').Read abouteasycheckYou will find more about assertions inthis article, entitled “Python Assertions, or Checking If a Cat Is a Dog” and published inTowards Data Science. It mentionseasycheck! You will read abouteasycheckalso inthe article “Comparing floating-point numbers with easycheck”(also fromTowards Data Science). TheBetter Programmingarticle entitiled“How to Overwrite AssertionError in Python and Use Custom Exceptions”, mentions the package, too.InstallingInstall and update using pip:pip install easycheckTestingThe package is covered with both pytests and doctests. The latter are included in both docstrings of all the functions, but also indocumentation files.Use in code to raise exceptionsHere are several examples of a simple use of basiceasycheckfunctions. The most basic usage resembles the following:check_if(a<10)This simply checks ifais smaller than 10; if it is, nothing happens (in fact,check_if(a < 10)returnsNone). But if the condition is violated, the function raisesAssertionError.AssertionErroris the default exception returned bycheck_if(), but you can change this:check_if(a<10,handle_with=ValueError)# or shorter and equally readable:check_if(a<10,ValueError)For built-in exceptions, likeValueError, the default behaviour is to not print any message. For custom exceptions, however, the exception’s docstring (.__doc__) serves as a message. You can use this when you create custom exceptions:classIncorrectNameTypeError(Exception):"""Argument name must be a string."""name=40check_type(name,IncorrectNameTypeError)Traceback(mostrecentcalllast):...IncorrectNameTypeError:Argumentnamemustbeastring.If you want to ensure that no message is printed, even for a custom exception, override the default behaviour by passing an empty stringmessage=''. You can also add a custom message:check_if(a<10,handle_with=ValueError,message='Too high a')# or shorter and equally readable:check_if(a<10,ValueError,'Too high a')Some other functions have different default errors; for instance, this callcheck_type(a,expected_type=str)# or shorter:check_type(a,str)will raiseTypeErrorwhile thischeck_length([1,2,3],1)will raiseLengthError(an exception class defined in theeasycheckmodule).Here is a list ofeasycheckcheck functions the module offers, discluding assertions, which are listed in the next paragraph:check_if(); it’s the most basiceasycheckfunction, similar to what you would get usingif;check_if_not(); the opposite ofcheck_if(), helpful when you need to assure that a condition isnotmet;check_if_isclose(); to compare two floating-point numbers, based onmatch.isclose()(seethis file);check_if_in_limits(); to check if a number lies between two other numbers;check_length(); to compare length (equal to, smaller than, greater than, and the like);check_type(); to check expected type, similar toisinstance();check_if_paths_exist(); to compare paths (or just one path) exist;check_comparison()(used to compare two items); to compare two objects, just like you would do usingif obj1 != obj2: raisecheck_all_ifs(); used to check multiple conditions and return all the checks;check_argument(); used to make one or more checks of a function’s argument.You can also use acatch_check()function, if you want to catch an exception or a warning theeasycheckfunction you use would raise (see exampleshere). Sometimes, however, you will do better using atry-exceptblock to catch exceptions (see examples).> Note that someeasycheckfunctions are simple wrappers around built-in functions, but their behavior is different, as they have the typical behavior of aneasycheckfunction: if a condition is not met, an exception is raised or an issue is raised.AssertionsIn addition to the above checking functions,easycheckprovides a set of functions for assertions. They can be used in both code and tests, just like regular assertions using theassertstatement. Assertion functions do have a specific functionality that makes them different from the corresponding check functions. You can read more about ithere. In short, assertions are called only in the development (non-production) mode, that is, when__debug__is set toTrue. An assertion should check a condition that should never happen; when the corresponding exception is raised, it means that something went wrong in the code, that something that should never happen has just happened.Some examples:You are working only on integers, for example pixels when rendering images, or placing objects on a board. You are sure that output will be integer, so you can assert on integers:defconvert_to_pixel_position(real_pos:tuple[float,float]):pos_x=real_pos[0]pos_y=real_pos[1]pixel_pos_x=round(pos_x)pixel_pos_y=round(pos_y)returnpixel_pos_x,pixel_pos_ypos=convert_to_pixel_position((1.2,3.4))assert_type(pos,tuple)assert_type(pos[0],int)Now consider a different example. Imagine you have output from somelen()method, or any other method calculating the length of something:out=len(example_object)# doing something with out, likenumber_of_elements_required=out*no_of_objectsassert_type(out_for_something_else,int)You are working on subset of some data. So the size of the data should not be larger than the initial one, but also not smaller than 0:defsubset_of(data:pd.DataFrame,filter_condition:callable)->pd.DataFrame:# create a data frame that is a subset of `data` based on `filter_condition`...x=pd.DataFrame({'x':[1,2,4],'y':[3,3,5]})x_subset=subset_of(x,lambdavalue:value<3)assert_if_in_limits(len(x_subset),0,len(x))Here is full list of supported assert functions:assert_if(); it’s the most basiceasycheckfunction, similar to what you would get usingif;assert_if_not(); the opposite ofassert_if(), helpful when you need to assure that a condition isnotmet;assert_if_isclose(); to assert whether two floating-point numbers are close enough, based onmatch.isclose()(seethis file);assert_if_in_limits(); to assert whether a number is in range of two other numbers;assert_length(); to assert length (equal to, smaller than, greater than, and the like);assert_type();to assert that an object has a particular type, as you would do usingassert isinstance;assert_paths(); to assert that a path exists or paths exist.Use in code to issue warningsIn order to issue a warning if a condition is violated, simply use a warning class (in thehandle_withargument) instead of an exception class:check_if(2>1,Warning,'Too high a value')check_length([1,2,3],10,Warning,'Too short list with data')Remember to always use a message with warnings, in order to make them meaningful. (See more inuse_with_warnings_doctest.rst).Of course, you can use a custom warning:classTooSmallSampleSize(Warning):"""Results for samples size below 100 can be unstable."""n=50check_if(n>=100,TooSmallSampleSize)...TooSmallSampleSize:Resultsforsamplessizebelow100canbeunstable.warnings.warn(message,error)Use in code, an exampleImagine you want to connect to a database; if the connection fails for any reason, you want to read an archived flat file. (We will use some undefined functions whose names will clearly convey what the functions do.)fromeasycheckimportcheck_if,check_if_paths_existclassDataBaseConnectionError(Exception):passdefget_data_from_db(db_details,db_credentials):try:connect_to_db(db_details,db_credentials)except:returnFalsedata=get_records_from_db()returndataTheeasycheckcode could look like the following:defget_data(db_details,db_credentials):data=get_data_from_db(db_details,db_credentials)check_if(data,handle_with=DataBaseConnectionError,message='Cannot communicate with the database')returndataYou can of course handle this exception, for example like here:defget_data(db_details,db_credentials,archived_data_file):data=get_data_from_db(db_details,db_credentials)try:check_if(data,handle_with=DataBaseConnectionError,message='Cannot communicate with the database')exceptDataBaseConnectionError:check_if_paths_exist(archived_data_file)withopen(archived_data_file)asf:data=f.readlines()returndataOf course, you might use here a dedicated context manager. Sure, you can write it in a shorter way, withouteasycheck, but the flow of information will not be as smooth, resulting in less readability:defget_data(db_details,db_credentials,archived_data_file):data=get_data_from_db(db_details,db_credentials)ifnotdata:withopen(archived_data_file)asf:data=f.readlines()returndataOf course, theopen()context manager will itself throw an error, but when you use thecheck_if()function and explicitly define an exception class, you clearly show the reader that you’re checking if this file exists and raise a particular exception if it doesn’t.Use in testingAs mentioned above, mosteasycheckfunctions have their asserts counterparts which can be used in testing. Of course, you can usecheck_if(), but to align with the common use of assertions, theeasycheckmodule offers those aliases so that the reader will immediately see that you’re using these functions to test. Consider these examples:# Using assertionsdeftest_something():a,b=my_function_1(),my_function_2()asserta==2;assertisinstance(a,int)assertisinstance(b,tuple)assertlen(b)==5# Using easycheck assert-like functions:deftest_something():a,b=my_function_1(),my_function_2()assert_if(a==2)assert_type(a,int)assert_type(b,tuple)assert_length(b,5)Note that only the first one will raiseAssertionErrorwhile the others will raise more meaningful errors (TypeErrorandLengthError), which may better explain the reasons that the tests did not pass.You will find more about usingeasycheckinuse_in_testing_doctest.rst.Other examplesYou will find a number of examples indoctest files, which also serve as doctests.Switching offeasycheckIf you want to maximize performance, you may wish to switch offeasycheckchecks. You would get the greatest increase in performance by removing (or commenting out) all calls toeasycheckfunctions, but this can be inconvenient. Hence,easycheckoffers you a more convenient way of doing so, namely, switching off via an environmental variable. This will be less efficient, as this will mean calling an empty function instead of actualeasycheckfunctions. While not the most performant, this approach can increase performance quite significantly. Its obvious advantage is that you do not need to do anything else than just setting the"EASYCHECK_RUN"environmental variable to"0":>EASYCHECK_RUN=0>pythonmy_script.pyThe my_script.py script will be run with alleasycheckfunctions replaced with an empty function.You can also switch off easycheck directly from Python:importosos.environ["EASYCHECK_RUN"]="0">Warning: Do remember to use this option wisely. While it will increase performance, it can also change the behavior of the Python program.ChangelogVersion 0.6.0 came with significant optimization of performance. Before,easycheckfunctions performed internal checks of the argument values provided to the function call. Most of these checks are not performed anymore, at least not for the most significanteasycheckfunctions, such ascheck_if()orcheck_type(). Some checks, however, are still done. These are mainly checks without which the behavior of the function would be either unwanted or unexpected. We decided to remove all checks that do not change much; for instance, they raise an error due to an incorrect type of an argument value â€” even though it would be raised anyway, but by the internal Python process, not by theeasycheckfunction itself. The point is to remove such unnecessary checks and that way remove the unnecessaryifblocks, which certainly add some cost to execution time. While one such check costs almost nothing, many of them (e.g., in a long loop) can mean a significant cost. As of version 0.6.0, we will try to optimize the performance ofeasycheckby getting rid of such overhead costs, unless they are important for the behavior of the correspondingeasycheckfunction.