affective-py 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- affective/__init__.py +13 -0
- affective/core/__init__.py +0 -0
- affective/core/continuation.py +8 -0
- affective/core/effects.py +55 -0
- affective/core/handlers.py +85 -0
- affective/core/loop.py +97 -0
- affective/mypy_ext/__init__.py +9 -0
- affective/mypy_ext/plugin.py +19 -0
- affective/py.typed +0 -0
- affective/std/__init__.py +0 -0
- affective/std/files.py +48 -0
- affective/std/http.py +75 -0
- affective/std/stdio.py +34 -0
- affective_py-0.1.0.dist-info/METADATA +7 -0
- affective_py-0.1.0.dist-info/RECORD +17 -0
- affective_py-0.1.0.dist-info/WHEEL +5 -0
- affective_py-0.1.0.dist-info/top_level.txt +1 -0
affective/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from affective.core.effects import Effect as Effect
|
|
2
|
+
from affective.core.effects import Raise as Raise
|
|
3
|
+
from affective.core.effects import Async as Async
|
|
4
|
+
from affective.core.effects import Affects as Affects
|
|
5
|
+
from affective.core.handlers import catch as catch
|
|
6
|
+
from affective.core.effects import operation as operation
|
|
7
|
+
from affective.core.continuation import \
|
|
8
|
+
RunningContinuation as RunningContinuation
|
|
9
|
+
from affective.core.continuation import Continuation as Continuation
|
|
10
|
+
from affective.core.handlers import handler as handler
|
|
11
|
+
from affective.core.loop import UnhandledEffect as UnhandledEffect
|
|
12
|
+
from affective.core.loop import run as run
|
|
13
|
+
from affective.core.loop import handle as handle
|
|
File without changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from collections.abc import Callable, Generator
|
|
2
|
+
from typing import Any, ParamSpec, TypeAlias
|
|
3
|
+
|
|
4
|
+
# bcz mkdocstrings does not understand PEP695
|
|
5
|
+
|
|
6
|
+
RunningContinuation: TypeAlias = Generator[Any, Any, Any]
|
|
7
|
+
_ExpectsInput = ParamSpec("_ExpectsInput")
|
|
8
|
+
Continuation: TypeAlias = Callable[_ExpectsInput, RunningContinuation]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from collections.abc import Generator, Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from functools import wraps
|
|
4
|
+
from typing import (
|
|
5
|
+
Annotated, Any, Awaitable, Sequence,
|
|
6
|
+
Mapping, final, cast, TypeVar
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
@final
|
|
12
|
+
class Perform:
|
|
13
|
+
effect_type: Any
|
|
14
|
+
effect_args: Sequence[Any]
|
|
15
|
+
effect_kwargs: Mapping[str, Any]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Effect:
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _StaticGeneratorMethod[T]:
|
|
23
|
+
# This is some black magic by mypy & Gemini
|
|
24
|
+
def __get__(self, instance: Any, owner: type | None = None) -> T:
|
|
25
|
+
raise NotImplementedError
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def operation[**P, R](f: Callable[P, Affects[R]]) -> _StaticGeneratorMethod[
|
|
29
|
+
Callable[P, Generator[Perform, Any, R]]
|
|
30
|
+
]:
|
|
31
|
+
@wraps(f)
|
|
32
|
+
def wrapper(
|
|
33
|
+
*args: P.args, **kwargs: P.kwargs
|
|
34
|
+
) -> Generator[Perform, R | Perform, R]:
|
|
35
|
+
ret = yield Perform(wrapper, args, kwargs)
|
|
36
|
+
while ret.__class__ is Perform:
|
|
37
|
+
ret = yield ret
|
|
38
|
+
return cast(R, ret)
|
|
39
|
+
|
|
40
|
+
return wrapper # type: ignore
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Raise[ExcT: Exception](Effect):
|
|
44
|
+
@operation
|
|
45
|
+
def error[_ExcT: Exception](err: _ExcT) -> Affects[None]: ...
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Async(Effect):
|
|
49
|
+
@operation
|
|
50
|
+
def wait[T](coro: Awaitable[T]) -> Affects[T, Raise[Exception]]: ...
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
type Affects[ReturnT, Effects = None] = Annotated[
|
|
54
|
+
Generator[Perform, Any, ReturnT], Effects
|
|
55
|
+
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from collections.abc import Callable, Generator
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any, Concatenate
|
|
4
|
+
|
|
5
|
+
from affective import Raise
|
|
6
|
+
from affective.core.effects import Perform
|
|
7
|
+
from affective.core.continuation import Continuation
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class OperationHandlerCollection:
|
|
12
|
+
handlers: dict[Any, Callable[
|
|
13
|
+
...,
|
|
14
|
+
Generator[Perform, Any, Any]
|
|
15
|
+
]]
|
|
16
|
+
|
|
17
|
+
def __add__(
|
|
18
|
+
self, other: Any
|
|
19
|
+
) -> "OperationHandlerCollection":
|
|
20
|
+
match other:
|
|
21
|
+
case OperationHandler(op, func):
|
|
22
|
+
return OperationHandlerCollection(self.handlers | {op: func})
|
|
23
|
+
case OperationHandlerCollection(handlers):
|
|
24
|
+
return OperationHandlerCollection(handlers | self.handlers)
|
|
25
|
+
case _:
|
|
26
|
+
return NotImplemented
|
|
27
|
+
|
|
28
|
+
def __radd__(self, other: Any) -> "OperationHandlerCollection":
|
|
29
|
+
return self.__add__(other)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class OperationHandler[**P = ..., R = Any]:
|
|
34
|
+
operation: Any
|
|
35
|
+
func: Callable[
|
|
36
|
+
Concatenate[Continuation[[R]], P],
|
|
37
|
+
Generator[Perform, Any, R]
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
def __add__(self, other: Any) -> "OperationHandlerCollection":
|
|
41
|
+
match other:
|
|
42
|
+
case OperationHandler(op, func):
|
|
43
|
+
return OperationHandlerCollection(
|
|
44
|
+
{op: func, self.operation: self.func}
|
|
45
|
+
)
|
|
46
|
+
case _:
|
|
47
|
+
return NotImplemented
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def handler[**P, R](
|
|
51
|
+
eff_op: Callable[P, Generator[Perform, Any, R]]
|
|
52
|
+
) -> Callable[
|
|
53
|
+
[
|
|
54
|
+
Callable[
|
|
55
|
+
Concatenate[Continuation[[R]], P],
|
|
56
|
+
Generator[Perform, Any, R]
|
|
57
|
+
]
|
|
58
|
+
],
|
|
59
|
+
OperationHandler
|
|
60
|
+
]:
|
|
61
|
+
def wrapper(
|
|
62
|
+
function: Callable[
|
|
63
|
+
Concatenate[Continuation[[R]], P],
|
|
64
|
+
Generator[Perform, Any, R]
|
|
65
|
+
]
|
|
66
|
+
) -> OperationHandler:
|
|
67
|
+
return OperationHandler(eff_op, function)
|
|
68
|
+
|
|
69
|
+
return wrapper
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def catch[
|
|
73
|
+
R,
|
|
74
|
+
ThenContT: Continuation[...]
|
|
75
|
+
](
|
|
76
|
+
on_catch: Continuation[[ThenContT, Exception]]
|
|
77
|
+
) -> OperationHandler:
|
|
78
|
+
@handler(Raise.error) # type: ignore
|
|
79
|
+
def _handler(
|
|
80
|
+
cont: ThenContT, exc: Exception
|
|
81
|
+
) -> Generator[Perform, R, R]:
|
|
82
|
+
ret: R = yield from on_catch(cont, exc)
|
|
83
|
+
return ret
|
|
84
|
+
|
|
85
|
+
return _handler
|
affective/core/loop.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from affective.core.effects import Async, Raise, Perform
|
|
4
|
+
from affective.core.handlers import (
|
|
5
|
+
OperationHandlerCollection,
|
|
6
|
+
OperationHandler,
|
|
7
|
+
)
|
|
8
|
+
from affective.core.continuation import RunningContinuation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UnhandledEffect(Exception):
|
|
12
|
+
def __init__(self, effect: Perform):
|
|
13
|
+
super().__init__(
|
|
14
|
+
f"Effect handler for {effect.effect_type.__qualname__} not found"
|
|
15
|
+
)
|
|
16
|
+
self.effect = effect
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handle(
|
|
20
|
+
ctx: OperationHandlerCollection | OperationHandler,
|
|
21
|
+
cont: RunningContinuation,
|
|
22
|
+
effect: Perform | None = None,
|
|
23
|
+
) -> Any:
|
|
24
|
+
if isinstance(ctx, OperationHandler):
|
|
25
|
+
ctx = OperationHandlerCollection({ctx.operation: ctx.func})
|
|
26
|
+
if effect is None:
|
|
27
|
+
try:
|
|
28
|
+
effect = next(cont)
|
|
29
|
+
except StopIteration as stop:
|
|
30
|
+
return stop.value
|
|
31
|
+
while True:
|
|
32
|
+
if not isinstance(effect, Perform):
|
|
33
|
+
raise TypeError(f"Unknown yield: {effect}")
|
|
34
|
+
if effect.effect_type in ctx.handlers:
|
|
35
|
+
def after(effect_result: Any) -> Any:
|
|
36
|
+
try:
|
|
37
|
+
eff = cont.send(effect_result)
|
|
38
|
+
except StopIteration as stop:
|
|
39
|
+
return stop.value
|
|
40
|
+
return_value = yield from handle(ctx, cont, eff)
|
|
41
|
+
return return_value
|
|
42
|
+
|
|
43
|
+
ret = yield from handle(
|
|
44
|
+
ctx, ctx.handlers[effect.effect_type](
|
|
45
|
+
after, *effect.effect_args, **effect.effect_kwargs
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
return ret
|
|
49
|
+
else:
|
|
50
|
+
effect_result = yield effect
|
|
51
|
+
try:
|
|
52
|
+
effect = cont.send(effect_result)
|
|
53
|
+
except StopIteration as stop:
|
|
54
|
+
return stop.value
|
|
55
|
+
return_value = yield from handle(ctx, cont, effect)
|
|
56
|
+
return return_value
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def run(cont: RunningContinuation) -> Any:
|
|
60
|
+
try:
|
|
61
|
+
effect = next(cont)
|
|
62
|
+
while True:
|
|
63
|
+
if not isinstance(effect, Perform):
|
|
64
|
+
raise TypeError(f"Unknown yield: {effect}")
|
|
65
|
+
if effect.effect_type == Raise.error:
|
|
66
|
+
raise effect.effect_args[0]
|
|
67
|
+
else:
|
|
68
|
+
raise UnhandledEffect(effect)
|
|
69
|
+
except StopIteration as stop:
|
|
70
|
+
return stop.value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def arun(cont: RunningContinuation) -> Any:
|
|
74
|
+
try:
|
|
75
|
+
effect = next(cont)
|
|
76
|
+
while True:
|
|
77
|
+
if not isinstance(effect, Perform):
|
|
78
|
+
raise TypeError(f"Unknown yield: {effect}")
|
|
79
|
+
if effect.effect_type == Raise.error:
|
|
80
|
+
raise effect.effect_args[0]
|
|
81
|
+
elif effect.effect_type == Async.wait:
|
|
82
|
+
try:
|
|
83
|
+
result = await effect.effect_args[0]
|
|
84
|
+
except Exception as exc:
|
|
85
|
+
try:
|
|
86
|
+
effect = cont.send(Perform(Raise.error, [exc], {}))
|
|
87
|
+
except StopIteration as stop:
|
|
88
|
+
return stop.value
|
|
89
|
+
else:
|
|
90
|
+
try:
|
|
91
|
+
effect = cont.send(result)
|
|
92
|
+
except StopIteration as stop:
|
|
93
|
+
return stop
|
|
94
|
+
else:
|
|
95
|
+
raise UnhandledEffect(effect)
|
|
96
|
+
except StopIteration as stop:
|
|
97
|
+
return stop.value
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Callable, Optional
|
|
2
|
+
|
|
3
|
+
from mypy.plugin import Plugin, ClassDefContext
|
|
4
|
+
from mypy.nodes import Decorator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def abstractise_effects(ctx: ClassDefContext) -> None:
|
|
8
|
+
for stmt in ctx.cls.defs.body:
|
|
9
|
+
if isinstance(stmt, Decorator):
|
|
10
|
+
stmt.func.abstract_status = 1
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AffectivePlugin(Plugin):
|
|
14
|
+
def get_base_class_hook(self, fullname: str) -> Optional[
|
|
15
|
+
Callable[[ClassDefContext], None]
|
|
16
|
+
]:
|
|
17
|
+
if fullname == "affective.core.effects.Effect":
|
|
18
|
+
return abstractise_effects
|
|
19
|
+
return None
|
affective/py.typed
ADDED
|
File without changes
|
|
File without changes
|
affective/std/files.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from affective import Effect, Affects, Raise, operation, Continuation, handler
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Files(Effect):
|
|
7
|
+
@operation
|
|
8
|
+
def write(path: str, contents: bytes) -> Affects[
|
|
9
|
+
None, Raise[PermissionError]
|
|
10
|
+
]: ...
|
|
11
|
+
|
|
12
|
+
@operation
|
|
13
|
+
def read(path: str) -> Affects[
|
|
14
|
+
bytes, Raise[PermissionError] | Raise[FileNotFoundError]
|
|
15
|
+
]: ...
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@handler(Files.write)
|
|
19
|
+
def _handle_default_write(
|
|
20
|
+
then: Continuation[[None]],
|
|
21
|
+
path: str,
|
|
22
|
+
contents: bytes
|
|
23
|
+
) -> Affects[Any]:
|
|
24
|
+
try:
|
|
25
|
+
with open(path, "wb") as f:
|
|
26
|
+
f.write(contents)
|
|
27
|
+
except PermissionError as exc:
|
|
28
|
+
yield from Raise.error(exc)
|
|
29
|
+
ret = yield from then(None)
|
|
30
|
+
return ret
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@handler(Files.read)
|
|
34
|
+
def _handle_default_read(
|
|
35
|
+
then: Continuation[[bytes]],
|
|
36
|
+
path: str,
|
|
37
|
+
) -> Affects[Any]:
|
|
38
|
+
try:
|
|
39
|
+
with open(path, "rb") as f:
|
|
40
|
+
contents = f.read()
|
|
41
|
+
except PermissionError as exc:
|
|
42
|
+
yield from Raise.error(exc)
|
|
43
|
+
else:
|
|
44
|
+
ret = yield from then(contents)
|
|
45
|
+
return ret
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
default_files_handler = _handle_default_read + _handle_default_write
|
affective/std/http.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from logging import Handler
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from aiohttp.web_response import Response
|
|
7
|
+
from aiohttp import ClientSession, ClientTimeout
|
|
8
|
+
except ImportError as exc:
|
|
9
|
+
raise ImportError(
|
|
10
|
+
"Cannot use Http effects without [aiohttp] extra"
|
|
11
|
+
) from exc
|
|
12
|
+
|
|
13
|
+
from affective import Async, Effect, handler, operation, Continuation, Affects
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class HttpResponse:
|
|
18
|
+
status_code: int
|
|
19
|
+
data: bytes
|
|
20
|
+
headers: dict[str, str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Http(Effect):
|
|
24
|
+
@operation
|
|
25
|
+
def request(
|
|
26
|
+
method: str,
|
|
27
|
+
url: str,
|
|
28
|
+
params: str | dict[str, Any] | None = None,
|
|
29
|
+
data: Any | None = None,
|
|
30
|
+
headers: dict[str, str] | None = None,
|
|
31
|
+
timeout_s: float | None = None,
|
|
32
|
+
) -> Affects[HttpResponse]:
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@handler(Http.request)
|
|
37
|
+
def _async_request_handler(
|
|
38
|
+
then: Continuation[[HttpResponse]],
|
|
39
|
+
method: str,
|
|
40
|
+
url: str,
|
|
41
|
+
params: str | dict[str, Any] | None = None,
|
|
42
|
+
data: Any | None = None,
|
|
43
|
+
headers: dict[str, str] | None = None,
|
|
44
|
+
timeout_s: float | None = None,
|
|
45
|
+
) -> Affects[Any, Async]:
|
|
46
|
+
async def _do() -> HttpResponse:
|
|
47
|
+
async with (
|
|
48
|
+
ClientSession() as session,
|
|
49
|
+
session.request(
|
|
50
|
+
method=method,
|
|
51
|
+
url=url,
|
|
52
|
+
params=params,
|
|
53
|
+
data=data,
|
|
54
|
+
headers=headers,
|
|
55
|
+
timeout=ClientTimeout(timeout_s)
|
|
56
|
+
if timeout_s is not None else None,
|
|
57
|
+
raise_for_status=False,
|
|
58
|
+
) as response
|
|
59
|
+
):
|
|
60
|
+
response_data = await response.read()
|
|
61
|
+
return HttpResponse(
|
|
62
|
+
status_code=response.status,
|
|
63
|
+
data=response_data,
|
|
64
|
+
headers={
|
|
65
|
+
header: value
|
|
66
|
+
for header, value in response.headers.items()
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
result = yield from Async.wait(_do())
|
|
71
|
+
ret = yield from then(result)
|
|
72
|
+
return ret
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async_http_handler = _async_request_handler
|
affective/std/stdio.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from affective import operation, handler, Effect, Affects
|
|
5
|
+
from affective.core.continuation import Continuation, RunningContinuation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Console(Effect):
|
|
9
|
+
@operation
|
|
10
|
+
def read() -> Affects[str]: ...
|
|
11
|
+
|
|
12
|
+
@operation
|
|
13
|
+
def write(text: str) -> Affects[None]: ...
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@handler(Console.write)
|
|
17
|
+
def default_stdout_writer(
|
|
18
|
+
then: Continuation[[None]], text: str
|
|
19
|
+
) -> Affects[Any]:
|
|
20
|
+
sys.stdout.write(text)
|
|
21
|
+
sys.stdout.flush()
|
|
22
|
+
ret = yield from then(None)
|
|
23
|
+
return ret
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@handler(Console.read)
|
|
27
|
+
def default_stdin_reader(
|
|
28
|
+
then: Continuation[[str]]
|
|
29
|
+
) -> Affects[Any]:
|
|
30
|
+
ret = yield from then(sys.stdin.readline().removesuffix("\n"))
|
|
31
|
+
return ret
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
default_stdio_handler = default_stdout_writer + default_stdin_reader
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
affective/__init__.py,sha256=N9Qj8r6viO9t9vWGv3qU1PGQ_-3AAmUmY_jLeGd9jZQ,700
|
|
2
|
+
affective/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
affective/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
affective/core/continuation.py,sha256=YhA7xDWOLvA3jyrSVye3D5yA9OMlHo56krI3bpISJO8,321
|
|
5
|
+
affective/core/effects.py,sha256=twF1ow-asfLpmWu57k89Y76fAE8LcJkLrlExFAAXfPA,1358
|
|
6
|
+
affective/core/handlers.py,sha256=re1o3P7hvMOxTSetjbsLspH05KqlVXmQn4xTGoThzyo,2316
|
|
7
|
+
affective/core/loop.py,sha256=m2LUMETFlx7vUHSMBGKUuzjK_qNt2OPBS87WNtyFUns,3337
|
|
8
|
+
affective/mypy_ext/__init__.py,sha256=SEGepM-7TBVSvsXbWOzO4VZD55EJ5d5C4hiycCe60I4,270
|
|
9
|
+
affective/mypy_ext/plugin.py,sha256=bT-cf3BXzu2CCBRAL6HSYdUREt7nSKN4G0Ma7S-EJhM,572
|
|
10
|
+
affective/std/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
affective/std/files.py,sha256=LyGJ0l00JyK5oAyo47DPf6xP3ghyXYIlInLj1idWI5A,1153
|
|
12
|
+
affective/std/http.py,sha256=bDKQX9Ec7YQ00qn-ArPoJv70Cs0abeBTOjdf2rz5kpA,2094
|
|
13
|
+
affective/std/stdio.py,sha256=OC2lyhRRKp7kIq65PPzpVkFaTX_HzK5yyaG7KE5M6Ms,804
|
|
14
|
+
affective_py-0.1.0.dist-info/METADATA,sha256=S5bTU31VlU2gB6FcbWYPqqyz_IT5evEDPO29GJOzX3I,188
|
|
15
|
+
affective_py-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
16
|
+
affective_py-0.1.0.dist-info/top_level.txt,sha256=3PaZbbPjBoUDa-uZoYamcf8haaV2WV1Kh_pVS8YcpwY,10
|
|
17
|
+
affective_py-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
affective
|