Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport Python 3.10 NewTypes #156

Closed
effigies opened this issue May 8, 2023 · 5 comments · Fixed by #157
Closed

Backport Python 3.10 NewTypes #156

effigies opened this issue May 8, 2023 · 5 comments · Fixed by #157

Comments

@effigies
Copy link

effigies commented May 8, 2023

As of Python 3.10, the following types are picklable:

from pathlib import Path
from typing import NewType

File = NewType("File", Path)
Directory = NewType("Directory", Path)

Prior to that, this fails. Would it be possible to backport the NewType class to Python 3.7+?

@AlexWaygood
Copy link
Member

Here you go: #157

@effigies
Copy link
Author

effigies commented May 8, 2023

Amazing. Thanks for being so quick!

@JelleZijlstra
Copy link
Member

@effigies may I ask why it's important to you that types are picklable? We have quite a lot of code to deal with pickling types and on first sight I don't really understand why people would care. This may be relevant to implementing new type system features in the future.

@effigies
Copy link
Author

effigies commented May 8, 2023

Sure. It's not a principled requirement so much as practically it's causing me problems while trying to add static type checking to an existing code-base that uses type annotations at runtime.

The context is a workflow engine where we use types to annotate inputs and outputs (basically attrs classes), for example:

@attr.s
class InputSpec:
    in_file: File = attr.ib(...)

@attr.s
class OutputSpec:
    out_file: File = attr.ib(...)

class MyTask(Task):
    input_spec = InputSpec
    output_spec = OutputSpec

    def run(self, runtime):
        # run task
        return runtime

This is a static example to give you the flavor. The MyTask class is sent to a runner along with a dictionary of inputs. We also have syntactic sugar for pure Python tasks:

@task
def mytask(in_file: File) -> File:
    ...

Here, the input/output specs are dynamically constructed. In order to dispatch them to run in separate interpreters or on other servers, some form of pickling is required. It's possible that there's a way to rewrite our code base to avoid it, but I found that when adding a NewType I had passing tests on Python 3.10 but errors like the following in Python 3.7-3.9:

concurrent.futures.process._RemoteTraceback: 
"""
Traceback (most recent call last):
  File "/Users/runner/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/concurrent/futures/process.py", line 205, in _sendback_result
    exception=exception))
  File "/Users/runner/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/multiprocessing/queues.py", line 358, in put
    obj = _ForkingPickler.dumps(obj)
  File "/Users/runner/hostedtoolcache/Python/3.7.16/x64/lib/python3.7/multiprocessing/reduction.py", line 51, in dumps
    cls(buf, protocol).dump(obj)
AttributeError: Can't pickle local object 'NewType.<locals>.new_type'
"""

To be honest, I'm not even sure if we'll end up making File/Directory into NewTypes, so I may not need this feature, but I think having consistent implementations across Python versions would at least help determine if NewType is the right tool for the job.

@JelleZijlstra
Copy link
Member

Thanks, that's helpful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants