dry-python / classes Goto Github PK
View Code? Open in Web Editor NEWSmart, pythonic, ad-hoc, typed polymorphism for Python
Home Page: https://classes.rtfd.io
License: BSD 2-Clause "Simplified" License
Smart, pythonic, ad-hoc, typed polymorphism for Python
Home Page: https://classes.rtfd.io
License: BSD 2-Clause "Simplified" License
Dependabot can't resolve your Python dependency files.
As a result, Dependabot couldn't update your dependencies.
The error Dependabot encountered was:
Creating virtualenv classes-Cg1ia3QJ-py3.9 in /home/dependabot/.cache/pypoetry/virtualenvs
Updating dependencies
Resolving dependencies...
PackageNotFound
Package sphinx-typlog-theme (0.8.0) not found.
at /usr/local/.pyenv/versions/3.9.0/lib/python3.9/site-packages/poetry/repositories/pool.py:144 in package
140│ self._packages.append(package)
141│
142│ return package
143│
→ 144│ raise PackageNotFound("Package {} ({}) not found.".format(name, version))
145│
146│ def find_packages(
147│ self, dependency,
148│ ):
If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.
Hi @sobolevn
It would be great to do something with an interface like this
@impl
def add(a: int, b: int) -> int:
return a + b
@impl
def add(a: str, b: str) -> str:
return '{0}{1}'.format(a, b)
If it's not a good idea to parse in runtime type annotations
@impl(int, int)
def add(a: int, int) -> int:
return a + b
@impl(str, str)
def add(a: str, b: str) -> str:
return '{0}{1}'.format(a, b)
What do you think?
Right now it is possible, but I can hack type_info.metadata
to forbid that.
Hi, love the project, I've definitely wanted better mypy support in singledispatch before, as well as other more advanced features.
That being said, it looks like mypy is going to add support for a singledispatch plugin. Maybe there's some opportunity for collaboration?
mypyc/mypyc#802
Dependabot can't resolve your Python dependency files.
As a result, Dependabot couldn't update your dependencies.
The error Dependabot encountered was:
Creating virtualenv classes-vz-MTj5d-py3.8 in /home/dependabot/.cache/pypoetry/virtualenvs
Updating dependencies
Resolving dependencies...
[PackageNotFound]
Package wemake-python-styleguide (0.13.0) not found.
If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.
class Foo:
def __call__(self, instance) -> A:
...
foo = typeclass(Foo)
...
class Bar:
def __call__(self, instance) -> B:
...
bar = typeclass(Bar)
...
def fn(instance: Supports[Foo, Bar]) -> C:
...
This is similar to #83 and python/typing#213, but offers the advantages of composability and conciseness.
I'd be willing to have a go at implementing this but I'm not very familiar with mypy plugins (where I suspect most of the work would be required).
Right now, it is possible to create AssociatedType
with bound type variables. This is not really supported during runtime.
So, we need a check for this.
Ok, I really thought that this is a good idea. But, now it seems to me that I was wrong.
Why?
def a(instance: Union[str, bytes])
. We can only work with Instance
types because of the limited runtime-dispatch capabilitiesdef ex():
...
some.instance(ex) # will typecheck, because we allow callables, but should not.
# So, we need a plugin support for this - and it is really complex, because it has lots of corner cases
Callable
should not be allowed into first .instance()
callgetattr(arg, '__annotations__')
and the thing is arg
might have __annotations__
even if it is not a function. So, I need to figure out another solution. But, probably the best way to solve this is to remove this featureSo, I have decided to remove this. Yes, it is a breaking changed, but there are just several users, and upgrade is really easy.
Reverts #136
Right now we don't test classes
with hypothesis
at all.
But, I have a great idea (at least it seems to me like so) of adding a hypothesis plugin, similar to one we have in returns
: https://github.com/dry-python/returns/blob/master/returns/contrib/hypothesis/laws.py
What should it do?
check_typeclass(typeclass_instance)
will check that for all existing types and protocols some valid return type is produced. For example, @typeclass def some(instance) -> str
must produce str
for all instance
, protocol
, and supported types and it must produce NotImplementedError
for types that are not supportedcheck_typeclass
must have allow_instances
, allow_protocols
, allow_unknown
kw bool
arguments to configure what types we actually need in a resulting strategyIt would be a nice companion to our mypy
check.
@Zac-HD do you like it? Maybe you have any other ideas?
Right now it is impossible to write a code like this one:
from classes import typeclass
from typing import List
@typeclass
def some(instance) -> int:
...
@some.instance(List[int])
def _some_list_int(instance: List[int]) -> int:
...
Because, you cannot check that some variable has type List[int]
in runtime.
Maybe we can use runtime typecheckers? Like: https://github.com/erezsh/runtype
Related #8
This code does not work:
@typeclass
def some(instance) -> str:
...
@some.instance(None)
def _some_none(instance: None) -> str:
...
But, it should. Otherwise, we won't have any way to register None
handler.
Btw, type(None)
does not work as well.
Right now there's assert isinstance(ctx.context, Decorator)
which explicitly forbids everything else.
Make sure to uncomment a test we have for this case in typesafety/
Dependabot couldn't find a Pipfile for this project.
Dependabot requires a Pipfile to evaluate your project's current Python dependencies. It had expected to find one at the path: /docs/requirements.txt/Pipfile
.
If this isn't a Python project, or if it is a library, you may wish to disable updates for it from within Dependabot.
Right now a person can write something like this:
class MyClass(AssociatedType):
x = 1
It does not make any sence, because MyClass
is only used for typing. Moreover, it can cause a lot of confusion and bad usage practices.
So, we need to validate that MyClass
is not used incorrectly.
We need to be sure that classes
and returns
can be used together and that typing works.
Plan:
returns
as dev-dependency
typesafety/test_integration/test_returns
folder where we would test itpytest-mypy-plugins
we would need to add returns_plugin
to mypy
settingsI am trying to implement a function which would deserialize arbitrary values from string representations. For example, I'd like to say
class Person(pydantic.BaseModel):
name: str
job: str
>>> from_json_string(Person, '{"name": "John Doe", "job": "baker"}')
Person(name='John Doe', job='baker')
Of course that requires an implementation for from_json_string
, something like this:
T = TypeVar('T')
@from_json_string.instance(pydantic.BaseModel)
def pydantic_from_json_string(model_class: Type[T], raw_value: str) -> T:
return model_class(**json.loads(raw_value))
I do not seem to be able to do this of course, since the library believes I am looking for instances of BaseModel whereas I am targeting the class itself.
@from_json_string.instance(Type[pydantic.BaseModel])
does not work because it does not match on generics.
Any workarounds?
We need to have a function similar to isinstance
to work with typeclasses.
We can might even use isinstance
itself. Or we can use protocols.
This code:
@typeclass
def test(instance) -> str:
...
@test.instance(str)
def _test_str(instance) -> str:
...
@test.instance(str) # error here!
def _test_str2(instance) -> str:
...
Should raise an error during type checking. Possibly also during runtime
This should be illegal: AssoicatedType[int]
Now we have a problem, because function args in Python are covariant we can use it incorrectly with a typeclass
.
Let's see an example:
from classes import typeclass
class A:
...
class B(A):
...
class C(B):
...
@typeclass
def some(instance) -> str:
...
@some.instance(B)
def _some_b(instance: B) -> str:
...
reveal_type(some(A())) # Should be an error
# Argument 1 to "some" has incompatible type "A"; expected "B"
reveal_type(some(B())) # ok
reveal_type(some(C())) # Should be an error, but is not
There's a common use-case when we need to create a type-class with several methods. Like:
class Functor(Protocol[T]):
def map(self, function: Callable[[T], Functor[V]]) -> Functor[V]:
...
@classmethod
def from_value(cls, inner_value: V) -> Functor[V]:
...
Currently, there's no way we can do that.
I propose something like this:
@typeclass
class Functor(Protocol[T]):
def map(self, function: Callable[[T], Functor[V]]) -> Functor[V]:
...
@classmethod
def from_value(cls, inner_value: V) -> Functor[V]:
...
I am not sure how to bound it to the actual implementation.
For example:
@typeclass
def some(instance): ...
@some.instance(int):
def _some_int1(instance: int): ...
@some.instance(int):
def _some_int2(instance: int): ...
There are several things we can do:
@typeclass(final=True)
, it won't allow exact duplicates when activated, but by default we will allow itWe need to be able to work with generic like List[str]
or Result[str, Exception]
and Maybe[int]
I propose the following API:
@typeclass
def test(instance) -> int:
...
@test.instance(
Some[int],
predicate=lambda instance: is_successful(instance) and isinstance(instance.unwrap(), int),
)
def _test_success_int(instance: Some[int]) -> int:
return instance.unwrap()
It should be supported on both side: types and runtime.
This code:
from classes import typeclass
def some():
@typeclass
def a(instance) -> str:
...
@a.instance(int)
def _a_int(instance: int) -> str:
...
reveal_type(a)
Produces this crash:
» mypy ex.py --show-traceback
ex.py:8: error: INTERNAL ERROR -- Please try using mypy master on Github:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 0.902
Traceback (most recent call last):
File "mypy/checker.py", line 401, in accept
File "mypy/nodes.py", line 777, in accept
File "mypy/checker.py", line 3586, in visit_decorator
File "mypy/checkexpr.py", line 936, in check_call
File "mypy/checkexpr.py", line 917, in check_call
File "mypy/checkexpr.py", line 1029, in check_callable_call
File "mypy/checkexpr.py", line 738, in apply_function_plugin
File "/Users/sobolev/Documents/github/typeclasses/classes/contrib/mypy/features/typeclass.py", line 161, in __call__
typeclass, fullname = self._load_typeclass(ctx.type.args[1], ctx)
File "/Users/sobolev/Documents/github/typeclasses/classes/contrib/mypy/features/typeclass.py", line 207, in _load_typeclass
typeclass = type_loader.load_typeclass(
File "/Users/sobolev/Documents/github/typeclasses/classes/contrib/mypy/typeops/type_loader.py", line 35, in load_typeclass
typeclass_info = ctx.api.lookup_qualified(fullname) # type: ignore
File "mypy/checker.py", line 4747, in lookup_qualified
File "mypy/checker.py", line 4743, in lookup
KeyError: 'Failed lookup: a'
ex.py:8: : note: use --pdb to drop into pdb
We need to turn this into a typing error.
I want to raise a mypy error on code like this:
@some.list(List[int])
def some_list(instance: List[int]): ...
Related #8
There are some cases right now, when Generic
typeclasses do not work as they should, for example:
from classes import typeclass
from typing import TypeVar
X = TypeVar('X')
@typeclass
def copy(instance: X) -> X:
...
@copy.instance(int)
def _copy_int(instance: int) -> int:
...
reveal_type(copy(1))
Right now it reports that signature is not correct, but it is.
ex.py:10: error: Instance callback is incompatible "def (instance: builtins.int) -> builtins.int"; expected "def [X] (instance: builtins.int) -> X`-1"
ex.py:10: error: Instance "builtins.int" does not match original type "X`-1"
ex.py:14: error: Argument 1 to "copy" has incompatible type "int"; expected <nothing>
Let's say you have this code:
from typing import TypeVar
from classes import typeclass, AssociatedType
X = TypeVar('X')
class Compare(AssociatedType):
...
@typeclass(Compare)
def compare(instance: X, other: X) -> X:
...
As you can see, your typeclass is generic. But, associated type is not.
It might lead to invalid type inference in some cases:
from typing import TypeVar, Iterable, List
from classes import typeclass, AssociatedType
X = TypeVar('X')
class GetItem(AssociatedType):
...
@typeclass(GetItem)
def get_item(instance: Iterable[X], index: int) -> X:
...
@get_item.instance(list)
def _get_item_list(instance: List[X], index: int) -> X:
...
reveal_type(get_item([1, 2, 3], 1))
# Revealed type is "<nothing>"
It can be fixed by modifing GetItem
type:
class GetItem(AssociatedType[X]):
...
reveal_type(get_item([1, 2, 3], 1))
# Revealed type is "builtins.int*"
We need to add an error message for this case.
Dependabot couldn't find a Pipfile for this project.
Dependabot requires a Pipfile to evaluate your project's current Python dependencies. It had expected to find one at the path: /docs/requirements.txt/Pipfile
.
If this isn't a Python project, or if it is a library, you may wish to disable updates for it from within Dependabot.
For example, one may want to define a typeclass with type restriction, like this:
@typeclass
def map_(instance: Functor[T], function: Callable[[T], V] -> V:
...
So, when defining instances, we would have to watch for this contract.
This will typecheck:
@map_.instance(Maybe)
def map_(instance: Maybe[T], function: Callable[[T], V] -> V:
return instance.map(function)
But, this will not typecheck:
@map_.instance(int)
def map_(instance: int, function: Callable[[int], V] -> V:
return function(instance)
When defining a typeclass, it is easy to forget that @typeclass
definition must not have a body.
Probably, we can check it with mypy
.
So, this must be an error:
@typeclass
def some(instance) -> None:
x = 1
It sould be possible to write a code like this:
a: Any
if some_typeclass.supports(a):
# "a" is "Supports[SomeTypeclass]" now
some_typeclass(a)
Hey @sobolevn!
functools.singledispatch
now supports using type annotations instead of passing an argument:
@singledispatch
def fun(arg, verbose=False):
...
@fun.register
def _(arg: int, verbose=False):
...
The old syntax still works, but in simple cases like here this helps reduce unnecessary repetition and, as a result, the code looks a bit cleaner.
Would you be interesting in accepting a PR implementing similar interface for _TypeClass.instance
?
This code does not work:
from classes import typeclass
class Meta(type):
def __instancecheck__(self, other) -> bool:
return other == 1
class Some(object, metaclass=Meta):
...
@typeclass
def some(instance) -> bool:
...
@some.instance(Some)
def _some_some(instance: Some) -> bool:
return True
print(some(1))
# NotImplementedError: Missing matched typeclass instance for type: int
But, it should! For example, phantom-types
use this method: https://github.com/antonagestam/phantom-types/blob/main/phantom/base.py#L28-L43
If we want to support them - we would need this feature.
I suggest that we need to add types with __instancecheck__
defined to both ._instances
and ._protocols
.
This way both of these cases would work:
print(some(Some()))
print(some(1))
When working with tuple
, we need to be sure that it has the porper size. For example:
from classes import typeclass
from typing import Tuple, TypeVar
X = TypeVar('X')
@typeclass
def some(instance) -> str:
...
@some.instance(tuple)
def _some_tuple(instance: Tuple[X, X]) -> X:
...
This code should not be allowed, because Tuple[X, X]
says that the size of instance
will be 2. But, we cannot guarantee it.
Instead, this should be used:
from classes import typeclass
from typing import Tuple, TypeVar
X = TypeVar('X')
@typeclass
def some(instance) -> str:
...
@some.instance(tuple)
def _some_tuple(instance: Tuple[X, ...]) -> X:
...
We need to copy this file from returns
: https://github.com/dry-python/returns/blob/master/.readthedocs.yml
Dependabot couldn't find a Pipfile for this project.
Dependabot requires a Pipfile to evaluate your project's current Python dependencies. It had expected to find one at the path: /docs/requirements.txt/Pipfile
.
If this isn't a Python project, or if it is a library, you may wish to disable updates for it from within Dependabot.
Dependabot can't resolve your Python dependency files.
As a result, Dependabot couldn't update your dependencies.
The error Dependabot encountered was:
Creating virtualenv classes-KwUktzQH-py3.9 in /home/dependabot/.cache/pypoetry/virtualenvs
Updating dependencies
Resolving dependencies...
PackageNotFound
Package sphinxcontrib-mermaid (0.4.0) not found.
at /usr/local/.pyenv/versions/3.9.0/lib/python3.9/site-packages/poetry/repositories/pool.py:144 in package
140│ self._packages.append(package)
141│
142│ return package
143│
→ 144│ raise PackageNotFound("Package {} ({}) not found.".format(name, version))
145│
146│ def find_packages(
147│ self, dependency,
148│ ):
If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.
Right now, this does not work:
- case: typeclass_generic_definition_associated_type
disable_cache: false
skip: true
main: |
from typing import List, TypeVar
from classes import typeclass, AssociatedType
X = TypeVar('X')
class Some(AssociatedType):
...
@typeclass(Some)
def some(instance, b: int) -> X:
...
@some.instance(list)
def _some_ex(instance: List[X], b: int) -> X:
return instance[b] # We need this line to test inner inference
reveal_type(some(['a', 'b'], 0)) # N: Revealed type is 'builtins.str*'
reveal_type(some([1, 2, 3], 0)) # N: Revealed type is 'builtins.int*'
Why?
Because the revealed signature of call is (instance: Union[List[X], Supports[Some]], b: int) -> X
and generic inference breaks for some reason. It always returns <nothing>
We also need something like this to work:
x: Supports[Some[int]]
reveal_type(some(x)) # Revealed type: int
Probably we can hack how Supports
is inserted into mro
by adding type args.
from typing import Iterable
from classes import typeclass
from dataclasses import dataclass
@typeclass
def greet(instance) -> str:
...
@greet.instance(Iterable, is_protocol=True)
def _greet_str(instance: Iterable) -> str:
return "Iterable"
@dataclass
class Point(object):
x:int
y:int
@greet.instance(Point)
def _greet_point(instance)-> str:
return f"Point({instance.x}-{instance.y})"
point: Point = Point(1,2)
print(greet(point))
print(greet([1, 2, 3]))
mypy.ini
[mypy]
ignore_missing_imports = True
allow_redefinition = false
check_untyped_defs = true
ignore_errors = false
implicit_reexport = false
local_partial_types = true
strict_optional = true
strict_equality = true
no_implicit_optional = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_unused_configs = true
warn_unreachable = true
warn_no_return = true
[mypy]
plugins =
classes.contrib.mypy.classes_plugin
I don't like current docs structure.
I need to rewrite it based on different topics:
Could a new patch/minor release be issued to prevent the classes plugin from crashing Mypy when installing via pip.
And possibly a __str__
as well.
Dependabot couldn't authenticate with https://pypi.python.org/simple/.
You can provide authentication details in your Dependabot dashboard by clicking into the account menu (in the top right) and selecting 'Config variables'.
The thing about AssociatedType
is that its subclasses can be used with any amount of type arguments.
Examples:
>>> from typing import TypeVar
>>> A = TypeVar('A')
>>> B = TypeVar('B')
>>> C = TypeVar('C')
>>> class WithOne(AssociatedType[A]):
... ...
>>> class WithTwo(AssociatedType[A, B]):
... ...
>>> class WithThree(AssociatedType[A, B, C]):
... ...
To achieve this I need:
Generic
does not allow itmypy
support, probably via get_type_analyze_hook
When disallow_untyped_defs
is enabled for Mypy the initial @typeclass
function will trigger a no-untyped-def
error:
from classes import typeclass
@typeclass
def meow(instance) -> None:
raise NotImplementedError(instance)
@meow.instance(str)
def _meow_str(instance: str) -> None:
print(f"meow {instance}")
$ mypy test.py
test.py: note: In function "meow":
test.py:5:1: error: Function is missing a type annotation for one or more arguments [no-untyped-def]
Found 1 error in 1 file (checked 1 source file)
We need this because of how dispatch is called:
Line 364 in 7e8ffd1
Related: python/mypy@c3ca0d6#diff-ffa623eda70646e703ce29c2bfe71e5d535dd0a0f0b785c094ed5c97cab6cf37R200
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.