Commit 5f4e4092 authored by Ontje Lünsdorf's avatar Ontje Lünsdorf
Browse files

Reintroduced BoundClass. This improves the online documentation of classes...

Reintroduced BoundClass. This improves the online documentation of classes (e.g. using help()) and is a more correct implementation (closer to the implementation of methods). Furthermore, this changeset reduces documentation fragmentation by moving more docstrings into the code files.
parent 99671797
......@@ -13,30 +13,7 @@ Performing the simulation of an environment
.. autofunction:: simulate
.. autoclass:: Environment
.. autoattribute:: active_process
.. autoattribute:: now
.. automethod:: exit
.. method:: event(self)
Returns a new instance of :class:`Event`.
.. method:: suspend(self)
Convenience method. Also returns a new instance of :class:`Event`.
.. method:: timeout(self, delay, value=None)
Returns a new instance of :class:`Timeout`.
.. method:: process(self, generator)
Returns a new instance of :class:`Process`.
.. method:: start(self, generator)
Convenience method. Also returns a new instance of :class:`Process`.
:members:
Events and helpers
......
......@@ -5,37 +5,7 @@
.. automodule:: simpy.resources.base
.. autoclass:: BaseResource
.. attribute:: PutQueue
The type to be used for the :attr:`put_queue`. This can either be
a plain :class:`list` (default) or a subclass of it.
.. attribute:: GetQueue
The type to be used for the :attr:`get_queue`. This can either be
a plain :class:`list` (default) or a sublcass of it.
.. attribute:: put_queue
Queue/list of events waiting to get something out of the resource.
.. attribute:: get_queue
Queue/list of events waiting to put something into the resource.
.. method:: put()
Create a new of :class:`Put` event.
.. method:: get()
Create a new of :class:`Get` event.
.. automethod:: _do_put
.. automethod:: _do_get
:members:
.. autoclass:: Put
:members:
......
......@@ -5,29 +5,11 @@
.. automodule:: simpy.resources.container
.. autoclass:: Container
.. autoattribute:: capacity
.. autoattribute:: level
.. method:: put(amount)
Creates a new :class:`ContainerPut` event.
.. method:: get(amount)
Creates a new :class:`ContainerGet` event.
:members:
.. autoclass:: ContainerPut
.. attribute:: amount
The amount to be put into the container.
:members:
.. autoclass:: ContainerGet
.. attribute:: amount
The amount to be taken out of the container.
:members:
......@@ -5,94 +5,26 @@
.. automodule:: simpy.resources.resource
.. autoclass:: Resource
.. attribute:: users
List of :class:`Request` events for the processes that are
currently using the resource.
.. attribute:: queue
Queue/list of pending :class:`Request` events that represent
processes waiting to use the resource.
.. autoattribute:: capacity
.. autoattribute:: count
.. method:: request()
Create a new :class:`Request` event.
.. method:: release()
Create a new :class:`Release` event.
:members:
.. autoclass:: PriorityResource
.. method:: request(priority=0)
Create a new :class:`PriorityRequest` event.
:members:
.. autoclass:: PreemptiveResource
:members:
.. method:: request(priority=0, preempt=True)
Create a new :class:`PriorityRequest` event with preemption enabled
by default.
.. autoclass:: Preempted(by, usage_since)
.. attribute:: by
The preempting process
.. attribute:: usage_since
The simulation time at which the preempted process started to use
the resource.
.. autoclass:: Preempted
:members:
.. autoclass:: Request
:members:
.. autoclass:: Release
.. attribute:: request
The request (:class:`Request`) that is to be released.
:members:
.. autoclass:: PriorityRequest
.. attribute:: priority
The priority of this request. A smaller number means higher
priority.
.. attribute:: preempt
Indicates wether the request should preempt a resource user or
not (this flag is not taken into account by
:class:`PriorityResource`).
.. attribute:: time
The simulation time at which the request was made.
.. attribute:: key
Key for sorting events. Consists of the priority (lower value is
more important) and the time at witch the request was made
(earlier requests are more important).
:members:
.. autoclass:: SortedQueue
.. attribute:: maxlen
Maximum length of the queue
.. automethod:: append
:members:
......@@ -5,48 +5,19 @@
.. automodule:: simpy.resources.store
.. autoclass:: Store
.. autoattribute:: capacity
.. attribute:: items
List of the items within the store.
.. method:: put(item)
Create a new :class:`StorePut` event.
.. method:: get()
Create a new :class:`StoreGet` event.
:members:
.. autoclass:: FilterStore
.. method:: get(filter=lambda item: True)
Create a new :class:`FilterStoreGet` event.
:members:
.. autoclass:: StorePut
.. attribute:: item
The item to put into the store.
:members:
.. autoclass:: StoreGet
:members:
.. autoclass:: FilterStoreGet(resource, filter=lambda item: True)
.. attribute:: filter
The filter function to use.
:members:
.. autoclass:: FilterQueue
.. automethod:: __getitem__
.. automethod:: __bool__
.. automethod:: __nonzero__
:members: __getitem__, __bool__, __nonzero__
......@@ -23,6 +23,29 @@ DEFAULT_PRIORITY = 1 # Default priority used by events
LOW_PRIORITY = 2 # Priority of timeouts
class BoundClass(object):
"""Allows classes to behave like methods. The ``__get__()`` descriptor is
basically identical to ``function.__get__()`` and binds the first argument
of the ``cls`` to the descriptor instance."""
def __init__(self, cls):
self.cls = cls
def __get__(self, obj, type=None):
if obj is None:
return self.cls
return types.MethodType(self.cls, obj)
@staticmethod
def bind_early(instance):
cls = type(instance)
for name in dir(cls):
obj = getattr(cls, name)
if type(obj) is BoundClass:
bound_class = getattr(instance, name)
setattr(instance, name, bound_class)
class Interrupt(Exception):
"""This exceptions is sent into a process if it was interrupted by
another process (see :func:`Process.interrupt()`).
......@@ -329,6 +352,7 @@ class Initialize(Event):
"""Initializes a process."""
def __init__(self, env, process):
self.env = env
self.name = None
self.ok = True
self._value = None
self.callbacks = [process._resume]
......@@ -528,9 +552,11 @@ class Scheduler(object):
class Environment(object):
"""The *environment* contains the simulation state and provides a
basic API for processes to interact with it.
"""The *environment* executes processes and manages access to global
information (such as the time, see :attr:`now`).
This class also provides aliases for common event types, for example:
:attr:`process`, :attr:`timeout` and :attr:`event`.
"""
def __init__(self, initial_time=0, scheduler=None):
self._active_proc = None
......@@ -539,25 +565,27 @@ class Environment(object):
scheduler = Scheduler(self, initial_time)
self.scheduler = scheduler
self.event = types.MethodType(Event, self)
self.suspend = types.MethodType(Event, self)
self.timeout = types.MethodType(Timeout, self)
self.process = types.MethodType(Process, self)
self.start = types.MethodType(Process, self)
self.schedule = self.scheduler.schedule
self.pop = self.scheduler.pop
@property
def active_process(self):
"""Property that returns the currently active process."""
return self._active_proc
BoundClass.bind_early(self)
@property
def now(self):
"""Property that returns the current simulation time."""
return self.scheduler.now
@property
def active_process(self):
"""Property that returns the currently active process."""
return self._active_proc
process = BoundClass(Process)
timeout = BoundClass(Timeout)
event = BoundClass(Event)
suspend = event
start = process
def exit(self, value=None):
"""Stop the current process, optionally providing a ``value``.
......
......@@ -7,9 +7,7 @@ modeled as an event that has to be yielded by the requesting process.
:class:`Put` and :class:`Get` are the base event types for this.
"""
import types
from simpy.core import Event, PENDING
from simpy.core import Event, PENDING, BoundClass
class Put(Event):
......@@ -158,16 +156,29 @@ class BaseResource(object):
"""
PutQueue = list
"""The type to be used for the :attr:`put_queue`. This can either be a
plain :class:`list` (default) or a subclass of it."""
GetQueue = list
"""The type to be used for the :attr:`get_queue`. This can either be a
plain :class:`list` (default) or a sublcass of it."""
def __init__(self, env):
self._env = env
self.put_queue = self.PutQueue()
"""Queue/list of events waiting to get something out of the
resource."""
self.get_queue = self.GetQueue()
"""Queue/list of events waiting to put something into the resource."""
# Bind event constructors as methods
BoundClass.bind_early(self)
put = BoundClass(Put)
"""Create a new :class:`Put` event."""
# Add event constructors as methods
self.put = types.MethodType(Put, self)
self.get = types.MethodType(Get, self)
get = BoundClass(Get)
"""Create a new :class:`Get` event."""
def _do_put(self, event):
"""Actually perform the *put* operation.
......
......@@ -12,6 +12,7 @@ station's storage tanks.
"""
import types
from simpy.core import BoundClass
from simpy.resources import base
......@@ -27,6 +28,8 @@ class ContainerPut(base.Put):
raise ValueError('amount(=%s) must be > 0.' % amount)
#: The amount to be put into the container.
self.amount = amount
"""The amount to be put into the container."""
super(ContainerPut, self).__init__(container)
......@@ -42,6 +45,8 @@ class ContainerGet(base.Get):
if amount <= 0:
raise ValueError('amount(=%s) must be > 0.' % amount)
self.amount = amount
"""The amount to be taken out of the container."""
super(ContainerGet, self).__init__(resource)
......@@ -74,10 +79,6 @@ class Container(base.BaseResource):
self._capacity = capacity
self._level = init
# Add event constructors as methods
self.put = types.MethodType(ContainerPut, self)
self.get = types.MethodType(ContainerGet, self)
@property
def capacity(self):
"""The maximum capactiy of the container. Read only."""
......@@ -91,6 +92,12 @@ class Container(base.BaseResource):
"""
return self._level
put = BoundClass(ContainerPut)
"""Creates a new :class:`ContainerPut` event."""
get = BoundClass(ContainerGet)
"""Creates a new :class:`ContainerGet` event."""
def _do_put(self, event):
if self._capacity - self._level >= event.amount:
self._level += event.amount
......
......@@ -22,13 +22,18 @@ other processes with a higher priority.
"""
import collections
import types
from simpy.core import BoundClass
from simpy.resources import base
Preempted = collections.namedtuple('Preempted', 'by, usage_since')
"""Used as interrupt cause for preempted processes."""
class Preempted(object):
def __init__(self, by, usage_since):
self.by = by
"""The preempting :class:`simpy.core.Process`."""
self.usage_since = usage_since
"""The simulation time at which the preempted process started to use
the resource."""
class Request(base.Put):
......@@ -62,6 +67,7 @@ class Release(base.Get):
"""
def __init__(self, resource, request):
self.request = request
"""The request (:class:`Request`) that is to be released."""
super(Release, self).__init__(resource)
......@@ -78,9 +84,17 @@ class PriorityRequest(Request):
"""
def __init__(self, resource, priority=0, preempt=True):
self.priority = priority
"""The priority of this request. A smaller number means higher
priority."""
self.preempt = preempt
"""Indicates wether the request should preempt a resource user or not
(this flag is not taken into account by :class:`PriorityResource`)."""
self.time = resource._env.now
"""The time at which the request was made."""
self.key = (self.priority, self.time)
"""Key for sorting events. Consists of the priority (lower value is
more important) and the time at witch the request was made (earlier
requests are more important)."""
super(PriorityRequest, self).__init__(resource)
......@@ -88,8 +102,8 @@ class SortedQueue(list):
"""Queue that sorts events by their ``key`` attribute."""
def __init__(self, maxlen=None):
super(SortedQueue, self).__init__()
#: Maximum length of the queue
self.maxlen = maxlen
"""Maximum length of the queue."""
def append(self, item):
"""Append *item* to the queue and keep the queue sorted."""
......@@ -120,11 +134,11 @@ class Resource(base.BaseResource):
super(Resource, self).__init__(env)
self._capacity = capacity
self.users = []
"""List of :class:`Request` events for the processes that are currently
using the resource."""
self.queue = self.put_queue
# Add event constructors as methods
self.request = types.MethodType(Request, self)
self.release = types.MethodType(Release, self)
"""Queue/list of pending :class:`Request` events that represent
processes waiting to use the resource."""
@property
def capacity(self):
......@@ -136,6 +150,12 @@ class Resource(base.BaseResource):
"""Number of users currently using the resource."""
return len(self.users)
request = BoundClass(Request)
"""Create a new :class:`Request` event."""
release = BoundClass(Release)
"""Create a new :class:`Release` event."""
def _do_put(self, event):
if len(self.users) < self.capacity:
self.users.append(event)
......@@ -159,12 +179,17 @@ class PriorityResource(Resource):
"""
PutQueue = SortedQueue
"""The type to be used for the
:attr:`~simpy.resources.base.BaseResource.put_queue`."""
GetQueue = SortedQueue
"""The type to be used for the
:attr:`~simpy.resources.base.BaseResource.get_queue`."""
def __init__(self, env, capacity=1):
super(PriorityResource, self).__init__(env, capacity)
# Add event constructors as methods
self.request = types.MethodType(PriorityRequest, self)
request = BoundClass(PriorityRequest)
"""Create a new :class:`PriorityRequest` event."""
class PreemptiveResource(PriorityResource):
......
......@@ -9,8 +9,7 @@ Beside :class:`Store`, there is a :class:`FilterStore` that lets you
use a custom function to filter the objects you get out of the store.
"""
import types
from simpy.core import BoundClass
from simpy.resources import base
......@@ -18,6 +17,7 @@ class StorePut(base.Put):
"""Put *item* into the store if possible or wait until it is."""
def __init__(self, resource, item):
self.item = item
"""The item to put into the store."""
super(StorePut, self).__init__(resource)
......@@ -36,6 +36,7 @@ class FilterStoreGet(StoreGet):
"""
def __init__(self, resource, filter=lambda item: True):
self.filter = filter
"""The filter function to use."""
super(FilterStoreGet, self).__init__(resource)
......@@ -93,16 +94,19 @@ class Store(base.BaseResource):
raise ValueError('"capacity" must be > 0.')
self._capacity = capacity
self.items = []
# Add event constructors as methods
self.put = types.MethodType(StorePut, self)
self.get = types.MethodType(StoreGet, self)
"""List of the items within the store."""
@property
def capacity(self):
"""The maximum capacity of the store."""
return self._capacity
put = BoundClass(StorePut)
"""Create a new :class:`StorePut` event."""
get = BoundClass(StoreGet)
"""Create a new :class:`StoreGet` event."""
def _do_put(self, event):
if len(self.items) < self._capacity:
self.items.append(event.item)
......@@ -135,13 +139,15 @@ class FilterStore(Store):
"""
GetQueue = FilterQueue
"""The type to be used for the
:attr:`~simpy.resources.base.BaseResource.get_queue`."""
def __init__(self, env, capacity=1):
super(FilterStore, self).__init__(env, capacity)
self.get_queue.store = self
# Add event constructors as methods
self.get = types.MethodType(FilterStoreGet, self)
get = BoundClass(FilterStoreGet)
"""Create a new :class:`FilterStoreGet` event."""
def _do_get(self, event):
for item in self.items:
......
......@@ -217,7 +217,7 @@ def test_preemptive_resource(env, log):
yield env.timeout(5)
log.append((env.now, id))
except simpy.Interrupt as ir: