\chapter{Hackish improvements to the \texttt{canvasapi} module}% ===> this file was generated automatically by noweave --- better not edit it

In this module we provide some decorators for the classes in the 
\texttt{canvasapi} package.
We automatically apply all decorators upon import, so
\begin{minted}{python}
import canvaslms.hacks.canvasapi_patch
\end{minted}
would apply all decorators defined herein to the already defined classes in the 
\texttt{canvasapi} package.

We do this as follows:
\nwfilename{canvasapi_patch.nw}\nwbegincode{1}\sublabel{NW3GUvJE-3N4MFK-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-3N4MFK-1}}}\moddef{canvasapi-patch.py~{\nwtagstyle{}\subpageref{NW3GUvJE-3N4MFK-1}}}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
"""A module that modifies the classes of the canvasapi package"""

import importlib
import inspect
import sys

\LA{}functions~{\nwtagstyle{}\subpageref{NW3GUvJE-nRuDO-1}}\RA{}

# Loads all hacks
this_module = sys.modules[__name__]

# automatically execute all functions in this module
for _, function in inspect.getmembers(this_module, inspect.isfunction):
  function()
\nwnotused{canvasapi-patch.py}\nwendcode{}\nwbegindocs{2}\nwdocspar


\section{Make classes comparable and hashable}

Since none of the classes in {\Tt{}canvasapi\nwendquote} defines the {\Tt{}{\_}{\_}eq{\_}{\_}\nwendquote} method, they 
all use the default which uses {\Tt{}is\nwendquote}.
However, in many cases, it makes more sense to actually compare what the 
objects represent.
Consider two {\Tt{}User\nwendquote} objects that represent the same user (the same Canvas 
ID), then they should be considered equal, even if the objects themselves are 
different.
\nwenddocs{}\nwbegincode{3}\sublabel{NW3GUvJE-nD6vy-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-nD6vy-1}}}\moddef{define decorator for comparable Canvas objects~{\nwtagstyle{}\subpageref{NW3GUvJE-nD6vy-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-1}}\nwenddeflinemarkup
def canvas_comparable(cls):
  def is_equal(self, other):
    """Tests if Canvas objects self and other refer to the same object"""
    return type(self) == type(other) and self.id == other.id

  cls.__eq__ = is_equal
  return cls
\nwused{\\{NW3GUvJE-nRuDO-1}}\nwendcode{}\nwbegindocs{4}\nwdocspar

The same applies for the hashable property.
A {\Tt{}User\nwendquote} object represents a fixed user that never changes, so we can use the 
type and Canvas ID to hash objects.
\nwenddocs{}\nwbegincode{5}\sublabel{NW3GUvJE-OWMC3-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-OWMC3-1}}}\moddef{define decorator for hashable Canvas objects~{\nwtagstyle{}\subpageref{NW3GUvJE-OWMC3-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-1}}\nwenddeflinemarkup
def canvas_hashable(cls):
  def canvas_hash(self):
    """Returns a hash suitable for Canvas objects"""
    return hash(type(self)) ^ hash(self.id)

  cls.__hash__ = canvas_hash
  return cls
\nwused{\\{NW3GUvJE-nRuDO-1}}\nwendcode{}\nwbegindocs{6}\nwdocspar

Adding these two, will allow us to put these objects into sets, for instance.
We sum it up in a function that can be run automatically when including this 
module.
\nwenddocs{}\nwbegincode{7}\sublabel{NW3GUvJE-nRuDO-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-nRuDO-1}}}\moddef{functions~{\nwtagstyle{}\subpageref{NW3GUvJE-nRuDO-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-3N4MFK-1}}\nwprevnextdefs{\relax}{NW3GUvJE-nRuDO-2}\nwenddeflinemarkup
def make_classes_comparable():
  """Improves the classes by adding __eq__ and __hash__ methods"""
  \LA{}define decorator for comparable Canvas objects~{\nwtagstyle{}\subpageref{NW3GUvJE-nD6vy-1}}\RA{}
  \LA{}define decorator for hashable Canvas objects~{\nwtagstyle{}\subpageref{NW3GUvJE-OWMC3-1}}\RA{}
  \LA{}improve eq method for classes~{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-1}}\RA{}
\nwalsodefined{\\{NW3GUvJE-nRuDO-2}}\nwused{\\{NW3GUvJE-3N4MFK-1}}\nwendcode{}\nwbegindocs{8}\nwdocspar

We want to do this for several classes.
\nwenddocs{}\nwbegincode{9}\sublabel{NW3GUvJE-1f4vnA-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-1}}}\moddef{improve eq method for classes~{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-1}}\nwprevnextdefs{\relax}{NW3GUvJE-1f4vnA-2}\nwenddeflinemarkup
# classes to improve in each module
CANVASAPI_CLASSES = \{
  "assignment": ["Assignment", "AssignmentGroup"],
  "submission": ["Submission"],
  "user": ["User"]
\}
\nwalsodefined{\\{NW3GUvJE-1f4vnA-2}\\{NW3GUvJE-1f4vnA-3}}\nwused{\\{NW3GUvJE-nRuDO-1}}\nwendcode{}\nwbegindocs{10}\nwdocspar

We then want to load all the relevant modules given above.
\nwenddocs{}\nwbegincode{11}\sublabel{NW3GUvJE-1f4vnA-2}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-2}}}\moddef{improve eq method for classes~{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-1}}\nwprevnextdefs{NW3GUvJE-1f4vnA-1}{NW3GUvJE-1f4vnA-3}\nwenddeflinemarkup
canvasapi_modules = \{\}

# import all modules
for module_name in CANVASAPI_CLASSES:
  canvasapi_modules[module_name] = \\
    importlib.import_module(f"canvasapi.\{module_name\}")
\nwused{\\{NW3GUvJE-nRuDO-1}}\nwendcode{}\nwbegindocs{12}\nwdocspar

Finally, we can go through all the modules and extract their members.
For each member, we check if it's a member to decorate, if so, we apply the 
decorators~{\Tt{}canvas{\_}comparable\nwendquote} and {\Tt{}canvas{\_}hashable\nwendquote} to it.
\nwenddocs{}\nwbegincode{13}\sublabel{NW3GUvJE-1f4vnA-3}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-3}}}\moddef{improve eq method for classes~{\nwtagstyle{}\subpageref{NW3GUvJE-1f4vnA-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-1}}\nwprevnextdefs{NW3GUvJE-1f4vnA-2}{\relax}\nwenddeflinemarkup
for module_name, module in canvasapi_modules.items():
  module_members = inspect.getmembers(module)
  for obj_name, obj in module_members:
    if obj_name in CANVASAPI_CLASSES[module_name]:
      canvas_comparable(obj)
      canvas_hashable(obj)
\nwused{\\{NW3GUvJE-nRuDO-1}}\nwendcode{}\nwbegindocs{14}\nwdocspar


\section{Improve User's {\Tt{}{\_}{\_}str{\_}{\_}\nwendquote} method}

By default, {\Tt{}canvasapi\nwendquote}'s {\Tt{}User\nwendquote} class defines a {\Tt{}{\_}{\_}str{\_}{\_}\nwendquote} dunder method 
that uses the user's name and Canvas ID.
We want to make it more useful, by using the user's name and login ID.
\nwenddocs{}\nwbegincode{15}\sublabel{NW3GUvJE-nRuDO-2}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-nRuDO-2}}}\moddef{functions~{\nwtagstyle{}\subpageref{NW3GUvJE-nRuDO-1}}}\plusendmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-3N4MFK-1}}\nwprevnextdefs{NW3GUvJE-nRuDO-1}{\relax}\nwenddeflinemarkup
def make_useful_user_dunder_str():
  """Improves the user class by changing __str__"""
  \LA{}define \code{}name{\_}and{\_}login\edoc{}~{\nwtagstyle{}\subpageref{NW3GUvJE-1wT12b-1}}\RA{}
  \LA{}update \code{}User.{\_}{\_}str{\_}{\_}\edoc{} to use \code{}name{\_}and{\_}login\edoc{}~{\nwtagstyle{}\subpageref{NW3GUvJE-3rEmL7-1}}\RA{}
\nwused{\\{NW3GUvJE-3N4MFK-1}}\nwendcode{}\nwbegindocs{16}\nwdocspar

Now, we simply need to define a function to use as a drop-in replacement for 
the {\Tt{}{\_}{\_}str{\_}{\_}\nwendquote} method.
\nwenddocs{}\nwbegincode{17}\sublabel{NW3GUvJE-1wT12b-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-1wT12b-1}}}\moddef{define \code{}name{\_}and{\_}login\edoc{}~{\nwtagstyle{}\subpageref{NW3GUvJE-1wT12b-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-2}}\nwenddeflinemarkup
def name_and_login(self):
  try:
    return f"\{self.name\} <\{self.login_id\}>"
  except AttributeError as err:
    return f"\{self.name\} <>"
\nwused{\\{NW3GUvJE-nRuDO-2}}\nwendcode{}\nwbegindocs{18}\nwdocspar

Then we simply need to replace the current {\Tt{}{\_}{\_}str{\_}{\_}\nwendquote} method with the new one 
above.
\nwenddocs{}\nwbegincode{19}\sublabel{NW3GUvJE-3rEmL7-1}\nwmargintag{{\nwtagstyle{}\subpageref{NW3GUvJE-3rEmL7-1}}}\moddef{update \code{}User.{\_}{\_}str{\_}{\_}\edoc{} to use \code{}name{\_}and{\_}login\edoc{}~{\nwtagstyle{}\subpageref{NW3GUvJE-3rEmL7-1}}}\endmoddef\nwstartdeflinemarkup\nwusesondefline{\\{NW3GUvJE-nRuDO-2}}\nwenddeflinemarkup
import canvasapi.user
canvasapi.user.User.__str__ = name_and_login
\nwused{\\{NW3GUvJE-nRuDO-2}}\nwendcode{}

\nwixlogsorted{c}{{canvasapi-patch.py}{NW3GUvJE-3N4MFK-1}{\nwixd{NW3GUvJE-3N4MFK-1}}}%
\nwixlogsorted{c}{{define \code{}name{\_}and{\_}login\edoc{}}{NW3GUvJE-1wT12b-1}{\nwixu{NW3GUvJE-nRuDO-2}\nwixd{NW3GUvJE-1wT12b-1}}}%
\nwixlogsorted{c}{{define decorator for comparable Canvas objects}{NW3GUvJE-nD6vy-1}{\nwixd{NW3GUvJE-nD6vy-1}\nwixu{NW3GUvJE-nRuDO-1}}}%
\nwixlogsorted{c}{{define decorator for hashable Canvas objects}{NW3GUvJE-OWMC3-1}{\nwixd{NW3GUvJE-OWMC3-1}\nwixu{NW3GUvJE-nRuDO-1}}}%
\nwixlogsorted{c}{{functions}{NW3GUvJE-nRuDO-1}{\nwixu{NW3GUvJE-3N4MFK-1}\nwixd{NW3GUvJE-nRuDO-1}\nwixd{NW3GUvJE-nRuDO-2}}}%
\nwixlogsorted{c}{{improve eq method for classes}{NW3GUvJE-1f4vnA-1}{\nwixu{NW3GUvJE-nRuDO-1}\nwixd{NW3GUvJE-1f4vnA-1}\nwixd{NW3GUvJE-1f4vnA-2}\nwixd{NW3GUvJE-1f4vnA-3}}}%
\nwixlogsorted{c}{{update \code{}User.{\_}{\_}str{\_}{\_}\edoc{} to use \code{}name{\_}and{\_}login\edoc{}}{NW3GUvJE-3rEmL7-1}{\nwixu{NW3GUvJE-nRuDO-2}\nwixd{NW3GUvJE-3rEmL7-1}}}%
\nwbegindocs{20}\nwdocspar

\nwenddocs{}
