Executors
The Executor object is the one who analyzes and processes the data that is instantiated with it. This type of object is the first core object we will see and the basis for all the others.
Executor at work
To instantiate an Executor object, you need two things: the mandatory one, a Dataset and the other optional is a header, which represents the data. Let’s see how to instantiate an Executor object.
import pyreports
# Create a data source
mydb = pyreports.manager('mysql', host='mysql1.local', database='test', user='dba', password='dba0000')
# Get data
mydb.execute('SELECT * FROM salary')
employees = mydb.fetchall() # return Dataset object
# Create Executor object
myex = pyreports.Executor(employees) # The employees object already has a header, as it was created by a database manager
Note
If I wanted to apply a header different from the name of the table columns, perhaps because they are not very speaking or full of underscores, I would have to instantiate the object as follows:
myex = pyreports.Executor(employees, header=['name', 'surname', 'salary'])
.
If you wanted to remove the header instead, just set it as None
: myex = pyreports.Executor(employees, header=None)
The Executor is a flexible object. It is not related to the pyreports library. An Executor can also be instantiated via its own Dataset or from a list of tuples (Python primitives used to instantiate a Dataset object. It is also equal to the return value of a database object)
import pyreports
import tablib
# Create my Dataset object
mydata = tablib.Dataset()
mydata.append(['Arthur', 'Dent', 55000])
mydata.append(['Ford', 'Prefect', 65000])
# Create Executor object: same result for both
myex = pyreports.Executor(mydata, header=['name', 'surname', 'salary'])
myex = pyreports.Executor([('Arthur', 'Dent', 55000), ('Ford', 'Prefect', 65000)], header=['name', 'surname', 'salary'])
# Set header after creation
myex.headers(['name', 'surname', 'salary'])
Filter data
One of the main functions of working with data is to filter it. The Executor object has a filter method for doing this. This method accepts a list of values that must correspond to one of the values of a row in the Executor’s Dataset.
Another way to filter the data of an Executor object is to pass a callable that takes a single argument and returns something.
The return value will be called by the bool
class to see if it is True
or False
.
This callable will be called to every single value of the row of the Executor’s Dataset.
Finally, it is possible to declare the name of a single return column, if not all columns are needed.
Note
You can pass both a list of values and a function to filter the data.
# Filter data by list
myex.filter([55000, 65000, 75000]) # Filter data only for specified salaries
# Filter data by callable
myex.filter(key=str.istitle) # Filter data only for string contains Title case
def big_salary(salary):
if not isinstance(salary, int):
return False
return True if salary >= 65000 else False # My custom function
myex.filter(key=big_salary) # Filter data with a salary greater than or equal to 65000
# Filter data by column
myex.filter(column='salary') # Filter by column: name
myex.filter(column=2) # Filter by column: index
# Filter data by list, callable and column
myex.filter([55000, 65000, 75000], str.istitle, 'salary') # Filter for all three methods
Warning
If the filters are not applied, the result will be an empty Executor object.
If you want to reapply a filter, you will have to reset the object, using the reset()
method. See below.
Map (modify) data
The Executor object is provided with a method to modify the data in real time.
The map
method accepts a mandatory argument, i.e. a callable that accepts a single argument and an optional one
that accepts the name of the column or the number of its index.
# Define my function for increase salary; isn't that amazing!
def salary_increase(salary):
if isinstance(salary, int):
if salary <= 65000:
return salary + 10000
return salary
# Let's go! Increase salary today!
myex.map(salary_increase)
# Now, return only salary columns
myex.map(salary_increase, column='salary')
Warning
If the function you are passing to the map method returns nothing, None
will be substituted for the original value.
If you are using special conditions make sure your function always returns to its original value.
Get data
An Executor is not a data object. It is an object that contains data for processing, filters and etc. Once an instance of an Executor object is created, the original data is saved so that it can be retrieved.
So there is a way to retrieve and print the current and original data.
# Get data
myex.get_data() # Return current Dataset object
myex.origin # Return original Dataset object
print(myex.get_data()) # Print Dataset with current data
# Assign result to variable
my_dataset = myex.get_data() # Return Dataset object
# Create a new executor
new_ex = pyreports.Executor(myex.get_data()) # New Executor object with current data
new_ex = myex.clone() # New Executor object with original data
Note
If you want to clone the original data contained in an Executor object, use the clone
method.
It is possible through this object, to restore the data source after the modification or the applied filter.
# Restore data
myex.reset() # Reset data to origin
print(myex.get_data())
Attention
Once the object is reset, any changes made will be lost, unless the object has been cloned.
Work with columns
Since the Executor object is based on a Dataset object, it is possible to work not only with rows but also with columns. Let’s see how to select a single column.
# Select column
myex.select_column(1) # Select column by index number (surname)
myex.select_column('surname') # Select column by name (surname)
We can also add columns as long as they are the same length as the others, otherwise, we will receive an InvalidDimension
exception.
# Add column with values
myex.add_column('floor', [1, 2])
# Add column with function values
def stringify_salary(row):
return f'$ {row[2]}'
myex.add_column('str_salary', stringify_salary)
Note
The function passed to the add_column
method must have a single argument representing the row (the name “row” is a convention).
You can use this argument to access data from other columns.
It is also possible to delete a column.
# Delete column
myex.del_column('floor')
Count
The Executor object contains data. You may need to count rows and columns.
The object supports the protocol for counting through the built-in len
function, which will return the current number of rows.
# Count columns
myex.count_columns() # Return number of columns
# Count rows
myex.count_rows() # Return number of rows
len(myex) # Return number of rows
Iteration
The Executor object supports the python iteration protocol (return of generator object). This means that you can use it in a for loop or in a list comprehension.
# For each row in Executor
for row in myex:
print(row)
# List comprehension
my_list_of_rows = [row for row in myex]