Search code examples
pythonpython-typingpyright

Can you do type checking in a called function?


I want to be able to call a function and have the caller be aware that the type of parameter has been checked. My type checker is Pyright through the Pylance extension in vscode with "basic" level of type checking enabled.

Code

Example class stubs

@dataclass()
class Personnel:
    person: Person
    employee: Optional[Employee]

@dataclass()
class Employee:
    department: str
    jobs: list[Job]

Current Implementation

The current pattern I'm using to get all of the jobs for every employee looks like this:

all_jobs = [
    job 
    for personnel in all_personnel_list if personnel.employee is not None
    job for job in personnel.employee.jobs
]

My type checker doesn't complain about accessing personnel.employee.jobs even though personnel.employee is an Optional because the type is checked in the if statement.

Desired Implementation

What I would like to be able to do is something like this:

def is_employee(x: Personnel):
    return x.employee is not None

all_jobs = [
    job 
    for personnel in all_personnel_list if is_employee(personnel)
    job for job in personnel.employee.jobs
]

The problem I'm facing is that my type checker, is giving me an error when I try this because it sees personnel.employee.jobs as still being Optional and warning me that I'm not handling the case where personnel.employee is None. I realize that having this helper method for such a simple example seems a little silly, but my actual use case would be using more complex helper methods and I'd like them to have descriptive names instead of being a chain of and statements.

I'm also not exactly sure of how to formally describe this problem, so I apologize if this is a duplicate.


Solution

  • Use typing.TypeIs for this:

    from typing import TypeIs
    
    def is_employee(x: object) -> TypeIs[Employee]:
        return isinstance(x, Employee)
    

    For your use-case typing.TypeGuard should also work:

    from typing import TypeGuard
    
    def is_employee(x: object) -> TypeGuard[Employee]:
        return isinstance(x, Employee)
    

    You can read the difference between the two in PEP 742