Mypy: Clarification of `properly subclassable class` in NewType

Created on 19 Apr 2019  Â·  6Comments  Â·  Source: python/mypy

I want to create a new type derived from ObjectId in similar way as is described in documentation for new type UserId derived from int:

from bson import ObjectId
UserId = NewType('UserId', ObjectId)

But Mypy gives Argument 2 to NewType(...) must be subclassable (got "Any").

There is written in documentation:

NewType accepts exactly two arguments. The first argument must be a string literal containing the name of the new type and must equal the name of the variable to which the new type is assigned. The second argument must be a properly subclassable class, i.e., not a type construct like Union, etc.

It is not clear what _properly subclassable class_ means and why ObjectId is not _properly subclassable class_. Could please someone clarify this to me and in documentation?

bug documentation false-positive priority-1-normal

Most helpful comment

Here is another vote for allowing a NewType from Any - with the reality of many libraries still lacking type stubs, allowing me to NewType their classes (which mypy thinks are Any) is a basic sort of safety feature that would really help me. Is there any chance of this happening? Or any workaround that anyone has discovered?

All 6 comments

The reason is presumably that mypy can't find stubs for bson, so it treats all names from it as Any, and it disallows using NewType on Any. I agree that this would be useful to mention in the docs.

I think we should actually allow Any as a base class for NewType. The logic is that Any should not cause any _new_ errors (of course unless one uses some --disallow-any-* flags).

But in addition we should clarify the docs.

Then we should also update PEP 484.

On Fri, Apr 19, 2019 at 4:29 PM Ivan Levkivskyi notifications@github.com
wrote:

I think we should actually allow Any as a base class for NewType. The
logic is that Any should not cause any new errors (of course unless one
uses some --disallow-any-* flags).

But in addition we should clarify the docs.

—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/python/mypy/issues/6701#issuecomment-485037387, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAWCWMRGXZKEQOYS4X2K65TPRJIVJANCNFSM4HHD5WBA
.

>

--Guido (mobile)

I'd also like to add support for https://github.com/python/mypy/issues/6701#issuecomment-485037387 - and wanted to share a particular use case that may offer motivation.

I'm working on an interface for optional third party Spreadsheet reading applications. Conceptually a Spreadsheet is pretty well defined, but the various readers and file formats have different ways of describing them. In the Excel world this could be named a "Workbook" whereas the OpenDocument world may just refer to it as a "Document".

I think my base class would ideally look something like:

Spreadsheet_T = NewType('Spreadsheet_T', Any)
Sheet_T = NewType('Sheet_T', Any)

class SpreadsheetReader(ABC):

    @property
    @abstractmethod
    def spreadsheet_cls(self) -> Type[Spreadsheet_T]:
        ...

    @abstractmethod
    def get_first_sheet(self, spreadsheet: Spreadsheet_T) -> Sheet_T:
        ...

So if I were interacting with openpyxl that subclass would look something like:

class OpenpyxlReader(SpreadsheetReader):

    @property
    def spreadsheet_cls(self) -> Type[Spreadsheet_T]:
        from openpyxl import Workbook
        return Workbook

    def get_first_sheet(self, spreadsheet: Spreadsheet_T) -> Sheet_T:
        return spreadsheet.worksheets[0]

Whereas something from the OpenDocument world would look as follows:

class OdfpyReader(SpreadsheetReader):

    @property
    def spreadsheet_cls(self) -> Type[Spreadsheet_T]:
        from odf.opendocument import OpenDocument
        return OpenDocument

    def get_first_sheet(self, spreadsheet: Spreadsheet_T) -> Sheet_T:
        from odf.table import Table
        tables = self.book.getElementsByType(Table)
        return tables[0]

This would obviously only be used for nominal typing but since these are optional third party dependencies for which I am only defining an interface I can't use that anyway.

Here is another vote for allowing a NewType from Any - with the reality of many libraries still lacking type stubs, allowing me to NewType their classes (which mypy thinks are Any) is a basic sort of safety feature that would really help me. Is there any chance of this happening? Or any workaround that anyone has discovered?

I think I found a workaround:

import typing as t

class AWSClient:
    def __getattr__(self, item: str) -> t.Any:
        pass


SQS = t.NewType('SQS', AWSClient)
S3 = t.NewType('S3', AWSClient)
Was this page helpful?
0 / 5 - 0 ratings