-
Notifications
You must be signed in to change notification settings - Fork 185
Description
Describe the issue
I have used canvasapi and it seems like a great project. I've noticed some DX issues that I'm offering to fix, if you'll let me.
Type Blindness Issues with PaginatedList
Demonstration
Consider the following code:
from canvasapi import Canvas
canvas = Canvas('base url', 'access token')
for course in canvas.get_courses(enrollment_status='active'):
print(course)Method and attribute discoverability are hampered by the type-blindness of PaginatedList. Here, it is evident my static analysis language server (pylance) only knows that Canvas(...).get_courses() returns a PaginatedList...

...but does not know the elements of PaginatedList are canvasapi.course.Course, as the documentation states.

This makes trying to figure out what is possible with course a minor nuisance, and requires looking at the documentation.

Suggestions
I do not presume to know how best to fix this issue, but I believe annotating PaginatedList with a TypeVar would fix the issue and provide better support for static analysis servers. It would look something like this:
from typing import TypeVar, Iterable
T = TypeVar('T')
class PaginatedList(object):
def __init__(
self,
content_class:T,
requester,
request_method,
first_url,
extra_attribs=None,
_root=None,
_url_override=None,
**kwargs
):
...
def __iter__(self) -> Iterator[T]:
...It might even be helpful to:
class PaginatedList(Iterable[T]):
...An example of this in the wild would be Tableau Server Client's Pager, which also de-paginates server responses.
Attribute Blindness
While documentation doesn't appear to have any issues with displaying available methods, it does appear to hide available attributes, which meant I was constantly using print(vars(...)) to figure out what attributes are available. Take, for example, the documentation for File, which does not mention the attributes 'category', 'content-type', 'created_at', 'created_at_date', 'display_name', 'filename', 'folder_id', 'hidden', 'hidden_for_user', 'id', 'lock_at', 'locked', 'locked_for_user', 'media_entry_id', 'mime_class', 'modified_at', 'modified_at_date', 'size', 'thumbnail_url', 'unlock_at', 'updated_at', 'updated_at_date', 'upload_status', 'url', 'uuid' and 'visibility_level'. It would be great if these were listed!
A static analysis tool also does not see these attributes:

The root cause appears to be that the underlying CanvasObject appears to be dynamically loading attributes. Having tried to maintain a python REST API wrapper myself, I understand what a pain it is to have to keep object models up to date, so I am sympathetic. However, Python already has a neat pattern for objects with dynamic attributes, though. It's called a dict! It supports syntactic sugar that CanvasObject does not. It's not a crime to have behavior-based classes, and behavior-based classes are allowed to have attributes, but how are you going to know if you want to .download() a File if it's not even clear that File has a display_name or a filename?
With 68 contributors and tooling like pydantic, swagger, and JSONSchema, it seems like it should be feasible to keep or even generate actual models of the Canvas API responses.
Take, for example, UVA's Canvas API documentation, which has example objects and defined models. Instructure even keeps a model change log.
Even API clients for massive applications such as Reddit have at least attribute lists in the documentation.
This is me officially volunteering to take a swing at at least a few objects and figure out how to maintain such a thing sustainably.