Skip to content

Commit 87d17d1

Browse files
authored
Merge pull request #2 from TheArtur128/development for 1.3.0 version
Code for 1.3.0 version
2 parents 8c7e205 + b6f1711 commit 87d17d1

File tree

4 files changed

+181
-87
lines changed

4 files changed

+181
-87
lines changed

README.md

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,86 @@
11
## Pyannotating
2-
Allows you to structure your annotations and put more information into them.<br>
3-
Templates created with this library should preferably be placed in the annotations.py file.
2+
Structures your annotations and puts more information into them.
43

54
### Installation
65
`pip install pyannotating`
76

8-
### Features
9-
You can create a template of your annotations
7+
### Examples
8+
Create annotation templates
109
```python
11-
from pyannotating import *
1210
from typing import Callable, Any, Optional, Iterable
1311

12+
from pyannotating import *
13+
14+
1415
handler_of = AnnotationTemplate(Callable, [[input_annotation], Any])
15-
```
16-
and then create an annotation by this template
17-
```python
1816
handler_of[int | float]
1917
```
20-
21-
what is equivalent
2218
```python
2319
Callable[[int | float], Any]
2420
```
2521

26-
Also you can nest templates inside each other
22+
in a nested way
2723
```python
28-
optional_handler_of = AnnotationTemplate(
24+
optional_reformer_of = AnnotationTemplate(
2925
Callable,
3026
[[input_annotation], AnnotationTemplate(Optional, [input_annotation])]
3127
)
3228

33-
optional_handler_of[int]
29+
optional_reformer_of[int]
3430
```
35-
36-
what results in
3731
```python
3832
Callable[[int], Optional[int]]
3933
```
4034

41-
and use input_annotation in conjunction with something else
35+
with non-strict input annotation
4236
```python
4337
summator_of = AnnotationTemplate(Callable, [[input_annotation | int, input_annotation], int])
4438
summator_of[float]
4539
```
46-
47-
to get
4840
```python
4941
Callable[[float | int, float], int]
5042
```
5143

52-
In addition, you can integrate comments with your annotations
44+
Integrate comments into annotations
5345
```python
5446
even = FormalAnnotation("Formal annotation of even numbers.")
5547

5648
number: even[int | float] = 42
5749
```
5850

59-
or annotate downcasts
51+
or subgroups of existing types
6052
```python
61-
def transform(collection: Special[range, Iterable]) -> Any:
62-
...
53+
natural_numbers = Subgroup(int, lambda number: number > 0)
54+
55+
isinstance(14, natural_numbers)
56+
isinstance(-1.2, natural_numbers)
57+
58+
64 in natural_numbers
59+
```
60+
```python
61+
True
62+
False
63+
True
6364
```
6465

65-
or just use some pre-made templates
66+
or downcasts
6667
```python
67-
many_or_one[Callable]
68-
method_of[int]
68+
def transform(numbers: Special[range, Iterable[int]], additional: Special[None] = None) -> Any:
69+
...
70+
71+
# Equals to
72+
73+
def transform(numbers: Iterable[int], additional: Any = None) -> Any:
74+
...
6975
```
7076

71-
for getting
77+
78+
or just some pre-made templates and annotations
79+
```python
80+
many_or_one[int | float]
81+
number
82+
```
7283
```python
73-
Callable | Iterable[Callable]
74-
Callable[[int, ...], Any]
84+
int | float | Iterable[int | float]
85+
int | float | complex
7586
```

pyannotating.py

Lines changed: 91 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,14 @@
11
from abc import ABC, abstractmethod
2-
from typing import Optional, Any, Union, Iterable, Self, Mapping, Final, Callable, _UnionGenericAlias
2+
from typing import Optional, Any, Union, Iterable, Self, Protocol, Final, TypeVar, Generic, Type, Callable, _UnionGenericAlias
33

44

5-
class FormalAnnotation:
6-
"""
7-
Class allowing to formally specify additional information about the input
8-
resource.
9-
10-
When annotating, returns the input.
11-
12-
Can be called via [] with some resource.
13-
"""
14-
15-
def __init__(self, instance_doc: Optional[str] = None):
16-
if instance_doc is not None:
17-
self.__doc__ = instance_doc
18-
19-
def __repr__(self) -> str:
20-
return f"<{self.__class__.__name__}>"
21-
22-
def __getitem__(self, resource: Any) -> Any:
23-
return resource
5+
__all__ = (
6+
"AnnotationTemplate", "FormalAnnotation", "Special", "Subgroup",
7+
"input_annotation", "number", "many_or_one", "method_of"
8+
)
249

2510

26-
class AnnotationFactory(ABC):
11+
class _AnnotationFactory(ABC):
2712
"""
2813
Annotation factory class.
2914
Creates annotation by input other.
@@ -46,7 +31,7 @@ def _create_full_annotation_by(self, annotation: Any) -> Any:
4631
"""Annotation Creation Method from an input annotation."""
4732

4833

49-
class InputAnnotationAnnotation:
34+
class _InputAnnotationAnnotation:
5035
"""
5136
Singleton class for the annotation of the conditional empty space, in which
5237
the input type in the CustomAnnotationFactory should be placed.
@@ -72,46 +57,50 @@ def __ror__(self, other: Any) -> Union:
7257
return Union[other, self]
7358

7459

75-
class AnnotationTemplate(AnnotationFactory):
60+
class _ItemStorage(Protocol):
61+
@abstractmethod
62+
def __getitem__(self, key: Any) -> Any:
63+
pass
64+
65+
66+
class AnnotationTemplate(_AnnotationFactory):
7667
"""
77-
AnnotationFactory class delegating the construction of another factory's
68+
_AnnotationFactory class delegating the construction of another factory's
7869
annotation.
7970
80-
When called, replaces the InputAnnotationAnnotation instances from its
71+
When called, replaces the _InputAnnotationAnnotation instances from its
8172
arguments and their subcollections with the input annotation.
8273
8374
Templateizes Union.
8475
76+
Recognize nesting of annotations only by `list` i.e
77+
```
78+
AnnotationTemplate(Callable, [[input_annotation], Any]) # works
79+
AnnotationTemplate(Callable, [(input_annotation, ), Any]) # does not work
80+
```
81+
8582
Delegates responsibilities to other templates when passing them as
8683
annotations.
8784
"""
8885

89-
def __init__(self, original_factory: Mapping, annotations: Iterable):
90-
self._original_factory = original_factory
86+
def __init__(self, factory: _ItemStorage, annotations: list):
87+
self._factory = factory
9188
self._annotations = tuple(annotations)
9289

93-
@property
94-
def original_factory(self) -> Mapping:
95-
return self._original_factory
96-
97-
@property
98-
def annotations(self) -> tuple:
99-
return self._annotations
100-
10190
def __repr__(self) -> str:
10291
return "{factory}{arguments}".format(
10392
factory=(
104-
self._original_factory.__name__
105-
if hasattr(self._original_factory, '__name__')
106-
else self._original_factory
93+
self._factory.__name__
94+
if hasattr(self._factory, '__name__')
95+
else self._factory
10796
),
10897
arguments=str(self.__recursively_format(self._annotations)).replace('\'', str())
10998
)
11099

111100
def _create_full_annotation_by(self, annotation: Any) -> Any:
112101
formatted_annotations = self.__get_formatted_annotations_from(self._annotations, annotation)
113102

114-
return self._original_factory[
103+
return self._factory[
115104
formatted_annotations[0]
116105
if len(formatted_annotations) == 1
117106
else formatted_annotations
@@ -127,10 +116,10 @@ def __get_formatted_annotations_from(self, annotations: Iterable, replacement_an
127116
formatted_annotations = list()
128117

129118
for annotation in annotations:
130-
if isinstance(annotation, InputAnnotationAnnotation):
119+
if isinstance(annotation, _InputAnnotationAnnotation):
131120
annotation = replacement_annotation
132121

133-
elif isinstance(annotation, Iterable) and not isinstance(annotation, str):
122+
elif isinstance(annotation, list):
134123
annotation = self.__get_formatted_annotations_from(
135124
annotation,
136125
replacement_annotation
@@ -170,6 +159,27 @@ def __recursively_format(self, collection: Iterable) -> list:
170159
return formatted_collection
171160

172161

162+
class FormalAnnotation:
163+
"""
164+
Class allowing to formally specify additional information about the input
165+
resource.
166+
167+
When annotating, returns the input.
168+
169+
Can be called via [] with some resource.
170+
"""
171+
172+
def __init__(self, instance_doc: Optional[str] = None):
173+
if instance_doc is not None:
174+
self.__doc__ = instance_doc
175+
176+
def __repr__(self) -> str:
177+
return f"<{self.__class__.__name__}>"
178+
179+
def __getitem__(self, resource: Any) -> Any:
180+
return resource
181+
182+
173183
class Special:
174184
"""
175185
Annotation class for formally specifying specific behavior for subclasses.
@@ -183,7 +193,7 @@ class Special:
183193
"""
184194

185195
def __class_getitem__(cls, annotation_resource: tuple[Any, Any] | Any) -> Any:
186-
if not isinstance(annotation_resource, Iterable):
196+
if not isinstance(annotation_resource, tuple):
187197
return Any
188198

189199
elif len(annotation_resource) != 2:
@@ -194,18 +204,54 @@ def __class_getitem__(cls, annotation_resource: tuple[Any, Any] | Any) -> Any:
194204
return annotation_resource[1]
195205

196206

197-
number: Final = int | float | complex
207+
_BasicT = TypeVar("_BasicT")
208+
209+
210+
class Subgroup(Generic[_BasicT]):
211+
"""
212+
Class for defining a subgroup in an already existing type.
213+
214+
Delegates the definition of a subgroup from a particular type to
215+
`determinant` attribute.
216+
"""
217+
218+
def __init__(self, type_: Type[_BasicT], determinant: Callable[[_BasicT], bool]):
219+
self._type = type_
220+
self._determinant = determinant
221+
222+
@property
223+
def type_(self) -> Type[_BasicT]:
224+
return self._type
225+
226+
@property
227+
def determinant(self) -> Callable[[_BasicT], bool]:
228+
return self._determinant
229+
230+
def __instancecheck__(self, instance: Any) -> bool:
231+
return self._has(instance)
232+
233+
def __contains__(self, object_: Any) -> bool:
234+
return self._has(object_)
235+
236+
def __repr__(self) -> str:
237+
return f"<{self.__class__.__name__} of {self._type.__name__}>"
238+
239+
def _has(self, object_: Any) -> bool:
240+
return isinstance(object_, self.type_) and self._determinant(object_)
241+
198242

199243
# Pre-created instance without permanent formal creation of a new one.
200-
input_annotation: Final[InputAnnotationAnnotation] = InputAnnotationAnnotation()
244+
input_annotation: Final[_InputAnnotationAnnotation] = _InputAnnotationAnnotation()
245+
246+
247+
number: Final = int | float | complex
201248

202249

203250
many_or_one: Final[AnnotationTemplate] = AnnotationTemplate(
204251
Union,
205252
[input_annotation, AnnotationTemplate(Iterable, [input_annotation])]
206253
)
207254

208-
209255
method_of: Final[AnnotationTemplate] = AnnotationTemplate(
210256
Callable,
211257
[[input_annotation, ...], Any]

setup.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
1-
from setuptools import setup, find_packages
1+
from setuptools import setup
22

33

4-
PACKAGE_NAME = 'pyannotating'
4+
PACKAGE_NAME = "pyannotating"
55

6-
VERSION = '1.2.0'
6+
VERSION = "1.3.0"
77

8-
with open('README.md') as readme_file:
8+
with open("README.md") as readme_file:
99
LONG_DESCRIPTION = readme_file.read()
1010

1111
setup(
1212
name=PACKAGE_NAME,
13-
description="Library to structure annotations in your code",
13+
description="Library of annotations for humans",
1414
long_description=LONG_DESCRIPTION,
1515
long_description_content_type="text/markdown",
16-
license_files = ('LICENSE',),
16+
license_files = ("LICENSE",),
1717
license="GNU General Public License v3.0",
1818
version=VERSION,
1919
url="https://github.com/TheArtur128/Pyannotating",
2020
download_url=f"https://github.com/TheArtur128/Pyannotating/archive/refs/tags/v{VERSION}.zip",
2121
author="Arthur",
2222
author_email="s9339307190@gmail.com",
23-
python_requires='>=3.11',
23+
python_requires=">=3.11",
2424
classifiers=["Programming Language :: Python :: 3.11"],
25-
keywords=['library', 'annotations', 'generation', 'templating', 'annotations', 'informing'],
25+
keywords=["library", "annotations", "generation", "templating", "annotations", "informing", "code-readability"],
2626
py_modules=[PACKAGE_NAME]
2727
)

0 commit comments

Comments
 (0)