asimpy 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.
- asimpy/__init__.py +8 -0
- asimpy/actions.py +26 -0
- asimpy/environment.py +121 -0
- asimpy/gate.py +57 -0
- asimpy/interrupt.py +25 -0
- asimpy/process.py +56 -0
- asimpy/queue.py +87 -0
- asimpy/resource.py +76 -0
- asimpy-0.1.0.dist-info/METADATA +27 -0
- asimpy-0.1.0.dist-info/RECORD +12 -0
- asimpy-0.1.0.dist-info/WHEEL +4 -0
- asimpy-0.1.0.dist-info/licenses/LICENSE.md +24 -0
asimpy/__init__.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""Discrete event simulation using async/await."""
|
|
2
|
+
|
|
3
|
+
from .environment import Environment as Environment
|
|
4
|
+
from .gate import Gate as Gate
|
|
5
|
+
from .interrupt import Interrupt as Interrupt
|
|
6
|
+
from .process import Process as Process
|
|
7
|
+
from .queue import Queue as Queue
|
|
8
|
+
from .resource import Resource as Resource
|
asimpy/actions.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Awaitable actions."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .environment import Environment
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseAction:
|
|
10
|
+
"""
|
|
11
|
+
Base of all internal awaitable actions. Simulation authors should not use this directly.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, env: "Environment"):
|
|
15
|
+
"""
|
|
16
|
+
Construct a new awaitable action.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
env: simulation environment.
|
|
20
|
+
"""
|
|
21
|
+
self._env = env
|
|
22
|
+
|
|
23
|
+
def __await__(self) -> Any:
|
|
24
|
+
"""Handle `await`."""
|
|
25
|
+
yield self
|
|
26
|
+
return None
|
asimpy/environment.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Simulation environment."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import heapq
|
|
5
|
+
from .actions import BaseAction
|
|
6
|
+
from .process import Process
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Environment:
|
|
10
|
+
"""Simulation environment."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, logging: bool = False):
|
|
13
|
+
"""
|
|
14
|
+
Construct a new simulation environment.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
logging: print log messages while executing.
|
|
18
|
+
"""
|
|
19
|
+
self.now = 0
|
|
20
|
+
self._queue = []
|
|
21
|
+
self._logging = logging
|
|
22
|
+
|
|
23
|
+
def immediate(self, proc: Process):
|
|
24
|
+
"""
|
|
25
|
+
Start a new process immediately.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
proc: `Process`-derived object to schedule and run.
|
|
29
|
+
"""
|
|
30
|
+
self.schedule(self.now, proc)
|
|
31
|
+
|
|
32
|
+
def schedule(self, time: float | int, proc: Process):
|
|
33
|
+
"""
|
|
34
|
+
Schedule a process to run at a specified time (*not* after a delay).
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
time: when the process should be scheduled to run.
|
|
38
|
+
proc: `Process`-derived object to run.
|
|
39
|
+
"""
|
|
40
|
+
heapq.heappush(self._queue, _Pending(time, proc))
|
|
41
|
+
|
|
42
|
+
def sleep(self, delay: float | int) -> "_Sleep":
|
|
43
|
+
"""
|
|
44
|
+
Suspend the caller for a specified length of time.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
delay: how long to sleep.
|
|
48
|
+
|
|
49
|
+
Returns: awaitable representing delay.
|
|
50
|
+
"""
|
|
51
|
+
return _Sleep(self, delay)
|
|
52
|
+
|
|
53
|
+
def run(self, until: float | int | None = None):
|
|
54
|
+
"""
|
|
55
|
+
Run the whole simulation.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
until: when to stop (run forever if not provided).
|
|
59
|
+
"""
|
|
60
|
+
while self._queue:
|
|
61
|
+
if self._logging:
|
|
62
|
+
print(self)
|
|
63
|
+
|
|
64
|
+
pending = heapq.heappop(self._queue)
|
|
65
|
+
|
|
66
|
+
if until is not None and pending.time > until:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
self.now = pending.time
|
|
70
|
+
proc = pending.proc
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
if proc._interrupt is None:
|
|
74
|
+
awaited = proc._coro.send(None)
|
|
75
|
+
else:
|
|
76
|
+
exc, proc._interrupt = proc._interrupt, None
|
|
77
|
+
awaited = proc._coro.throw(exc)
|
|
78
|
+
|
|
79
|
+
awaited.act(proc)
|
|
80
|
+
|
|
81
|
+
except StopIteration:
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
def __str__(self) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Format environment as printable string.
|
|
87
|
+
|
|
88
|
+
Returns: string representation of environment time and queue.
|
|
89
|
+
"""
|
|
90
|
+
return f"Env(t={self.now}, {' | '.join(str(p) for p in self._queue)})"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ----------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class _Pending:
|
|
98
|
+
"""Keep track of a waiting process."""
|
|
99
|
+
|
|
100
|
+
time: float
|
|
101
|
+
proc: Process
|
|
102
|
+
|
|
103
|
+
def __lt__(self, other: "_Pending") -> bool:
|
|
104
|
+
return self.time < other.time
|
|
105
|
+
|
|
106
|
+
def __str__(self) -> str:
|
|
107
|
+
return f"Pend({self.time}, {self.proc})"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class _Sleep(BaseAction):
|
|
111
|
+
"""Wait for a specified simulated time."""
|
|
112
|
+
|
|
113
|
+
def __init__(self, env: Environment, delay: float | int):
|
|
114
|
+
super().__init__(env)
|
|
115
|
+
self._delay = delay
|
|
116
|
+
|
|
117
|
+
def act(self, proc: Process):
|
|
118
|
+
self._env.schedule(self._env.now + self._delay, proc)
|
|
119
|
+
|
|
120
|
+
def __str__(self) -> str:
|
|
121
|
+
return f"_Sleep({self._delay})"
|
asimpy/gate.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Gate that holds multiple processes until flagged."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from .actions import BaseAction
|
|
5
|
+
from .process import Process
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .environment import Environment
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Gate:
|
|
12
|
+
"""Gate that multiple processes can wait on for simultaneous release."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, env: "Environment"):
|
|
15
|
+
"""
|
|
16
|
+
Construct a new gate.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
env: simulation environment.
|
|
20
|
+
"""
|
|
21
|
+
self._env = env
|
|
22
|
+
self._waiting = []
|
|
23
|
+
|
|
24
|
+
async def wait(self):
|
|
25
|
+
"""Wait until gate is next opened."""
|
|
26
|
+
await _Wait(self)
|
|
27
|
+
|
|
28
|
+
async def release(self):
|
|
29
|
+
"""Release all waiting processes."""
|
|
30
|
+
await _Release(self)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# ----------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class _Wait(BaseAction):
|
|
37
|
+
"""Wait at the gate."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, gate: Gate):
|
|
40
|
+
super().__init__(gate._env)
|
|
41
|
+
self._gate = gate
|
|
42
|
+
|
|
43
|
+
def act(self, proc: Process):
|
|
44
|
+
self._gate._waiting.append(proc)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _Release(BaseAction):
|
|
48
|
+
"""Release processes waiting at gate."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, gate: Gate):
|
|
51
|
+
super().__init__(gate._env)
|
|
52
|
+
self._gate = gate
|
|
53
|
+
|
|
54
|
+
def act(self, proc: Process):
|
|
55
|
+
while self._gate._waiting:
|
|
56
|
+
self._env.schedule(self._env.now, self._gate._waiting.pop())
|
|
57
|
+
self._env.schedule(self._env.now, proc)
|
asimpy/interrupt.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Interruption exceptions."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Interrupt(Exception):
|
|
7
|
+
"""Custom exception class for interruptions."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, cause: Any):
|
|
10
|
+
"""
|
|
11
|
+
Construct a new interruption exception.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
cause: reason for interruption.
|
|
15
|
+
"""
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.cause = cause
|
|
18
|
+
|
|
19
|
+
def __str__(self) -> str:
|
|
20
|
+
"""
|
|
21
|
+
Format interruption as printable string.
|
|
22
|
+
|
|
23
|
+
Returns: string representation of interruption and cause.
|
|
24
|
+
"""
|
|
25
|
+
return f"Interrupt({self.cause})"
|
asimpy/process.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Base class for processes."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any, TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .interrupt import Interrupt
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .environment import Environment
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Process(ABC):
|
|
13
|
+
"""Base class for active processes."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, env: "Environment", *args: Any):
|
|
16
|
+
"""
|
|
17
|
+
Construct a new process by performing common initialization,
|
|
18
|
+
calling the user-defined `init()` method (no underscores),
|
|
19
|
+
and registering the coroutine created by the `run()` method
|
|
20
|
+
with the environment.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
env: simulation environment.
|
|
24
|
+
*args: to be passed to `init()` for custom initialization.
|
|
25
|
+
"""
|
|
26
|
+
self.env = env
|
|
27
|
+
self._interrupt = None
|
|
28
|
+
|
|
29
|
+
self.init(*args)
|
|
30
|
+
|
|
31
|
+
self._coro = self.run()
|
|
32
|
+
self.env.immediate(self)
|
|
33
|
+
|
|
34
|
+
def init(self, *args: Any, **kwargs: Any) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Default (do-nothing) post-initialization method.
|
|
37
|
+
|
|
38
|
+
To satisfy type-checking, derived classes must also declare `*args`
|
|
39
|
+
rather than listing specific parameters by name.
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
def interrupt(self, cause: Any):
|
|
44
|
+
"""
|
|
45
|
+
Interrupt this process by raising an `Interrupt` exception the
|
|
46
|
+
next time the process is scheduled to run.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
cause: reason for interrupt (attacked to `Interrupt` exception).
|
|
50
|
+
"""
|
|
51
|
+
self._interrupt = Interrupt(cause)
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def run(self):
|
|
55
|
+
"""Actions for this process."""
|
|
56
|
+
pass
|
asimpy/queue.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""Queues."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from .actions import BaseAction
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseQueue(ABC):
|
|
8
|
+
def __init__(self, env):
|
|
9
|
+
self._env = env
|
|
10
|
+
self._gets = []
|
|
11
|
+
|
|
12
|
+
async def get(self):
|
|
13
|
+
return await _Get(self)
|
|
14
|
+
|
|
15
|
+
async def put(self, obj):
|
|
16
|
+
await _Put(self, obj)
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def _dequeue(self):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def _empty(self):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def _enqueue(self, obj):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Queue(BaseQueue):
|
|
32
|
+
def __init__(self, env):
|
|
33
|
+
super().__init__(env)
|
|
34
|
+
self._items = []
|
|
35
|
+
|
|
36
|
+
def _dequeue(self):
|
|
37
|
+
assert len(self._items) > 0
|
|
38
|
+
return self._items.pop(0)
|
|
39
|
+
|
|
40
|
+
def _enqueue(self, obj):
|
|
41
|
+
self._items.append(obj)
|
|
42
|
+
|
|
43
|
+
def _empty(self):
|
|
44
|
+
return len(self._items) == 0
|
|
45
|
+
|
|
46
|
+
def __str__(self):
|
|
47
|
+
return f"Queue({', '.join(str(i) for i in self._items)})"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ----------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _Get(BaseAction):
|
|
54
|
+
def __init__(self, queue):
|
|
55
|
+
super().__init__(queue._env)
|
|
56
|
+
self._queue = queue
|
|
57
|
+
self._proc = None
|
|
58
|
+
self._item = None
|
|
59
|
+
|
|
60
|
+
def act(self, proc):
|
|
61
|
+
if self._queue._empty():
|
|
62
|
+
self._proc = proc
|
|
63
|
+
self._queue._gets.append(self)
|
|
64
|
+
else:
|
|
65
|
+
self._item = self._queue._dequeue()
|
|
66
|
+
self._env.schedule(self._env.now, proc)
|
|
67
|
+
|
|
68
|
+
def __await__(self):
|
|
69
|
+
yield self
|
|
70
|
+
return self._item
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class _Put(BaseAction):
|
|
74
|
+
def __init__(self, queue, obj):
|
|
75
|
+
super().__init__(queue._env)
|
|
76
|
+
self._queue = queue
|
|
77
|
+
self.obj = obj
|
|
78
|
+
|
|
79
|
+
def act(self, proc):
|
|
80
|
+
self._queue._enqueue(self.obj)
|
|
81
|
+
|
|
82
|
+
if len(self._queue._gets) > 0:
|
|
83
|
+
waiting_get = self._queue._gets.pop(0)
|
|
84
|
+
waiting_get._item = self._queue._dequeue()
|
|
85
|
+
self._env.schedule(self._env.now, waiting_get._proc)
|
|
86
|
+
|
|
87
|
+
self._env.schedule(self._env.now, proc)
|
asimpy/resource.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Shared resource with limited capacity."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
from .actions import BaseAction
|
|
5
|
+
from .process import Process
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .environment import Environment
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Resource:
|
|
12
|
+
"""Shared resource with limited capacity."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, env: "Environment", capacity: int = 1):
|
|
15
|
+
"""
|
|
16
|
+
Create a new resource.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
env: simulation environment.
|
|
20
|
+
capacity: maximum simultaneous users.
|
|
21
|
+
"""
|
|
22
|
+
self._env = env
|
|
23
|
+
self._capacity = capacity
|
|
24
|
+
self._count = 0
|
|
25
|
+
self._waiting = []
|
|
26
|
+
|
|
27
|
+
async def acquire(self):
|
|
28
|
+
"""Acquire one unit of the resource."""
|
|
29
|
+
await _Acquire(self)
|
|
30
|
+
|
|
31
|
+
async def release(self):
|
|
32
|
+
"""Release one unit of the resource."""
|
|
33
|
+
await _Release(self)
|
|
34
|
+
|
|
35
|
+
async def __aenter__(self):
|
|
36
|
+
"""Acquire one unit of the resource using `async with`."""
|
|
37
|
+
await self.acquire()
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
async def __aexit__(self, exc_type, exc, tb):
|
|
41
|
+
"""Release one unit of the resource acquired with `async with`."""
|
|
42
|
+
await self.release()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ----------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class _Acquire(BaseAction):
|
|
49
|
+
"""Acquire a resource."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, resource: Resource):
|
|
52
|
+
super().__init__(resource._env)
|
|
53
|
+
self._resource = resource
|
|
54
|
+
|
|
55
|
+
def act(self, proc: Process):
|
|
56
|
+
if self._resource._count < self._resource._capacity:
|
|
57
|
+
self._resource._count += 1
|
|
58
|
+
self._env.schedule(self._env.now, proc)
|
|
59
|
+
else:
|
|
60
|
+
self._resource._waiting.append(proc)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _Release(BaseAction):
|
|
64
|
+
"""Release a resource."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, resource: Resource):
|
|
67
|
+
super().__init__(resource._env)
|
|
68
|
+
self._resource = resource
|
|
69
|
+
|
|
70
|
+
def act(self, proc: Process):
|
|
71
|
+
self._resource._count -= 1
|
|
72
|
+
if self._resource._waiting:
|
|
73
|
+
next_proc = self._resource._waiting.pop(0)
|
|
74
|
+
self._resource._count += 1
|
|
75
|
+
self._env.schedule(self._env.now, next_proc)
|
|
76
|
+
self._env.schedule(self._env.now, proc)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asimpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A simple discrete event simulator using async/await
|
|
5
|
+
Author-email: Greg Wilson <gvwilson@third-bit.com>
|
|
6
|
+
Maintainer-email: Greg Wilson <gvwilson@third-bit.com>
|
|
7
|
+
License-File: LICENSE.md
|
|
8
|
+
Keywords: discrete event simulation,open source
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: build>=1.4.0; extra == 'dev'
|
|
15
|
+
Requires-Dist: markdown-include>=0.8.1; extra == 'dev'
|
|
16
|
+
Requires-Dist: mkdocs-material>=9.7.1; extra == 'dev'
|
|
17
|
+
Requires-Dist: mkdocs>=1.6.1; extra == 'dev'
|
|
18
|
+
Requires-Dist: mkdocstrings[python]>=1.0.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: ruff>=0.14.10; extra == 'dev'
|
|
20
|
+
Requires-Dist: taskipy>=1.14.1; extra == 'dev'
|
|
21
|
+
Requires-Dist: twine>=6.2.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ty>=0.0.11; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# asimpy
|
|
26
|
+
|
|
27
|
+
A simple discrete event simulation framework in Python using `async`/`await`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
asimpy/__init__.py,sha256=BJoqSPhuG6zSHkEqYh8OBZPrqbAn52EYg9Hjf236xRk,298
|
|
2
|
+
asimpy/actions.py,sha256=G9f4Zt3Mgt1dvwOgXKbdZwmoy8Z-7pxVvre2C1J-WD0,546
|
|
3
|
+
asimpy/environment.py,sha256=9KooPy0v4gERqyX7H1O7cJQyYmTA04wWT8WExrqHAGI,3098
|
|
4
|
+
asimpy/gate.py,sha256=MFepFLcb8TNhzbaRAF5jJFFbprbRB-Q5phf1uMdl3LA,1381
|
|
5
|
+
asimpy/interrupt.py,sha256=G925y6jHB6b6y30sbq4e3jjutZDIW7Dwg9YY0mgf24Y,572
|
|
6
|
+
asimpy/process.py,sha256=6LRQRHzHI8PVt_AtckSQgsAPqv65BlUNJ8xOOVx9bXs,1550
|
|
7
|
+
asimpy/queue.py,sha256=qQOmQfuZ_MBS5aRax7pEuWrR6swlRmI-MU0iiWD6s-0,1938
|
|
8
|
+
asimpy/resource.py,sha256=O18VJARLIZHbvntOxtTfYefGO5Zibz3EcsKWretIlxE,2107
|
|
9
|
+
asimpy-0.1.0.dist-info/METADATA,sha256=oArpUAofvClqpvODhBOE_DyY2vyPWPZikHOTSQrA8MY,1044
|
|
10
|
+
asimpy-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
11
|
+
asimpy-0.1.0.dist-info/licenses/LICENSE.md,sha256=IjTDUvBk8xdl_n50CG1Vtk4FYdrS-C3uEYrRWAoOQqQ,1066
|
|
12
|
+
asimpy-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# MIT License
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
|
4
|
+
obtaining a copy of this software and associated documentation
|
|
5
|
+
files (the "Software"), to deal in the Software without
|
|
6
|
+
restriction, including without limitation the rights to use,
|
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the
|
|
9
|
+
Software is furnished to do so, subject to the following
|
|
10
|
+
conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
Copyright (c) Greg Wilson.
|