I'm trying to add SimPy simulation to a project I'm working on and I have some confusion about version 3's release/request.
I was able to implement resources using a 'with'-block without trouble but in my situation I want to request/release a resource without using a 'with'-block.
However, I cannot find an example of this using SimPy 3. I read the documentation/source regarding resources but still can't get it quite right. Could someone explain how to properly:
...
Request a Resource with the method: 'request()'
...
Release that Resource with the method: 'release()'
...
Thanks, and sorry for the bother.
PS: I'm intending to use Resources.resource
If you want to use a resource without a with block (and you know you won’t get interrupted), its just:
req = resource.request()
yield req
# do stuff
resource.release(req)
Using with on an object calls __enter__ when you enter the with block, and __exit__ when you leave. So when you do
res = resource.Resource()
with res.request() as req:
# stuff
You're really calling __enter__ on a Request object, doing #stuff then calling __exit__:
class Request(base.Put):
def __exit__(self, exc_type, value, traceback):
super(Request, self).__exit__(exc_type, value, traceback)
self.resource.release(self)
class Put(Event): # base.Put
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
# If the request has been interrupted, remove it from the queue:
if not self.triggered:
self.resource.put_queue.remove(self)
So, the with block is equivalent to this:
res = resource.Resource(...)
req = res.request()
#stuff
if not req.triggered:
res.put_queue.remove(req)
res.release(req)
However, the with block is also making sure that the cleanup code is called no matter what exceptions are thrown during #stuff. You'll lose that with the above code.
It's all outlined in PEP343;
with EXPR as VAR:
BLOCK
becomes:
mgr = (EXPR)
exit = type(mgr).__exit__ # Not calling it yet
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # Only if "as VAR" is present
BLOCK
except:
# The exceptional case is handled here
exc = False
if not exit(mgr, *sys.exc_info()):
raise
# The exception is swallowed if exit() returns true
finally:
# The normal and non-local-goto cases are handled here
if exc:
exit(mgr, None, None, None)
This is exactly how python uses with... as... blocks, but I'm presuming there's some reason you don't want to use these. If that's the case, then you just need the __enter__ and __exit__ functions. The way i think of it, is __enter__ sets everything up, and __exit__ does all the cleanup.
Related
I am trying to implement the retry decorator on a serial query. A general idea of my code is shown below. I am struggling to get it to retry when the decorator is one method up in the hierarchy. How can I have the method be retried when it's one method up from the method that throws the exception?
One complication that is frustrating is my increment time per retry depends on the actual command. Some commands require more time than others. That's why I have the extra_time_per_retry passed in, and couldn't implement the retry decorator using the traditional #retry style.
FYI the _serial is created in the class on init via pySerial.
I got it to work with the retry decorator directly above the method that throws the exception. I would like it to be two above to keep my code clean.
I have tried feeding the retry decorator the exact exception type, but couldn't get it to work.
def _query_with_retries(self, cmd, extra_time_per_retry):
_retriable_query = retry(stop_max_attempt_number=5,
wait_incrementing_start=self._serial.timeout + extra_time_per_retry,
wait_incrementing_increment=10)(self._query)
return _retriable_query(cmd)
def _query(self, cmd):
cmd_msg = cmd + '\r'
self._serial.reset_input_buffer()
self._serial.reset_output_buffer()
self._serial.write(cmd_msg)
return self._readlines()
def _readlines(self):
response_str = self._serial.read_until('\r', 256) # Max 256 bytes
# Parse response here, if a bad one set bad_response = true
if bad_response:
raise ResponseError("Response had custom error xyz")
I guess you could pack your call to _readlines() into an exception handling block, reraising the error:
python 3.x
#retry
def _query(self, cmd):
cmd_msg = cmd + '\r'
self._serial.reset_input_buffer()
self._serial.reset_output_buffer()
self._serial.write(cmd_msg)
try:
answer = self._readlines()
except Exception as e:
raise e
return answer
python 2.x
#retry
def _query(self, cmd):
cmd_msg = cmd + '\r'
self._serial.reset_input_buffer()
self._serial.reset_output_buffer()
self._serial.write(cmd_msg)
try:
answer = self._readlines()
except Exception:
t, v, tb = sys.exc_info()
raise t, v, tb
return answer
This way, you catch the exception directly when it occurs, and raise it inside the method which will be retried. I am not sure whether this declutters enough for you, however it should work.
Some might complain about using a blank except Exception, however since I am reraising it always immediately, I do not see any harm.
I am trying to wrap a .NET library in nice pythonic wrappers for use in IronPython.
A pattern used often in this library is a PersistenceBlock to make database CRUD operations clean and 'all or nothing':
try:
Persistence.BeginNewTransaction()
# do stuff here
Persistence.CommitTransaction()
except Exception, e:
Persistence.AbortTransaction()
Log.Error(e)
finally:
Persistence.CloseTransaction()
I would like to wrap this in a class that allows this kind of code:
with PersistenceBlock:
# do stuff here
this is what I've come up with:
class PersistenceBlock():
def __init__(self):
def __enter__(self):
return self
def __exit__(self, exctype, excinst, exctb):
try:
Persistence.BeginNewTransaction()
yield
Persistence.CommitTransaction()
except:
Persistence.AbortTransaction()
Log.Error(e)
finally
Persistence.CloseTransaction()
Is this a proper implementation of PEP343? What might I be missing?
The main thing that is throwing me is that Persistence is a static .NET class, and so there is not 'instance' to manage in the normal sense.
I have tried searching, but the word 'with' overwhelms the resutls :(
You can find the docs via searching for context manager protocol - that's the protocol all objects supposed to work with the with statement should implement.
Context managers (i.e. the __enter__ method) does not need to return anything - only if you want to use the with ... as ... syntax. In your __exit__ method you'll have to do some proper error checks: re-raise the exceptions if there are any and commit if there aren't. Maybe like this:
class PersistenceContext():
def __enter__(self):
# when opening the block, open a transaction
Persistence.BeginNewTransaction()
def __exit__(self, exctype, excinst, exctb):
if excinst is None:
# all went well - commit & close
Persistence.CommitTransaction()
Persistence.CloseTransaction()
else:
# something went wrong - abort, close and raise the error
Persistence.AbortTransaction()
Persistence.CloseTransaction()
raise exctype, excinst, exctb
For completeness, you could also use the contextmanager decorator to implement your context using a simple generator:
import contextlib
#contextlib.contextmanager
def PersisenceContext():
try:
yield Persistence.BeginNewTransaction()
except Exception:
Persistence.AbortTransaction()
raise
else:
Persistence.CommitTransaction()
finally:
Persistence.CloseTransaction()
From within an __exit__ block in a custom cursor class I want to catch an exception so I can in turn throw a more specific exception. What is the proper way to do this?
class Cursor:
def __enter__(self):
...
def __exit__(self, ex_type, ex_val, tb):
if ex_type == VagueThirdPartyError:
# get new more specific error based on error code in ex_val and
# return that one in its place.
return False # ?
else:
return False
Raising the specific exception within the __exit__ block seems like a hack, but maybe I'm over thinking it.
The proper procedure is to raise the new exception inside of the __exit__ handler.
You should not raise the exception that was passed in though; to allow for context manager chaining, in that case you should just return a falsey value from the handler. Raising your own exceptions is however perfectly fine.
Note that it is better to use the identity test is to verify the type of the passed-in exception:
def __exit__(self, ex_type, ex_val, tb):
if ex_type is VagueThirdPartyError:
if ex_val.args[0] == 'foobar':
raise SpecificException('Foobarred!')
# Not raising a new exception, but surpressing the current one:
if ex_val.args[0] == 'eggs-and-ham':
# ignore this exception
return True
if ex_val.args[0] == 'baz':
# re-raise this exception
return False
# No else required, the function exits and `None` is returned
You could also use issubclass(ex_type, VagueThirdPartyError) to allow for subclasses of the specific exception.
I was reading this article and it was showing this interesting bit of code:
class Car(object):
def _factory_error_handler(self):
try:
yield
except FactoryColorError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
def _create_customizer_error_handler(self, vin):
try:
yield
except CustomizerError, err:
self._factory.remove_car(vin)
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
def _update_customizer_error_handler(self, vin):
try:
yield
except CustomizerError, err:
stacktrace = sys.exc_info()[2]
raise ValidationError(err.message), None, stacktrace
def create(self, color, stereo):
with self._factory_error_handler():
vin = self._factory.make_car(color)
with self._create_customizer_error_handler(vin):
self._customizer.update_car(vin, stereo)
return vin
def update(self, vin, color, stereo):
with self._factory_error_handler():
self._factory.update_car_color(vin, color)
with self._update_customizer_error_handler(vin):
self._customizer.update_car(vin, stereo)
return
Here, they are yielding with no argument in a try block. And then using it within a with block. Can someone please explain what is going on here?
The code in the post appears to be wrong. It would make sense if the various error handlers were decorated with contextmanager. Note that, in the post, the code imports contextmanager but doesn't use it. This makes me think the person just made a mistake in creating the post and left contextmanager out of that example. (The later examples in the post do use contextmanager.) I think the code as posted will cause an AttributeError, because the various _error_handler functions aren't context managers and don't have the right __enter__ and __exit__ methods.
With contextmanager, the code makes sense based on the documentation:
At the point where the generator yields, the block nested in the with statement is executed. The generator is then resumed after the block is exited. If an unhandled exception occurs in the block, it is reraised inside the generator at the point where the yield occurred.
I was curious if this causes any bad behaviors. I ran a test case and got no errors so I assume its OK (although probably not good practice). Just wanted to know how python deals with the issue I assumed should have existed?
with open("somefile.txt","r") as fileinfo:
fileinfo = fileinfo.readlines()
print fileinfo
I thought overwritting "fileinfo" would cause issues exiting the with statement (raise some error about not being able to .close() a list). Does the with statement retain a local copy of the file reference? Thanks!
Of course Python retains an internal reference to the object used in the with statement. Otherwise how would it work when you don't use the as clause?
A with statement does indeed store a local reference to the file object (although I am not positive exactly what is stored in self.gen)
Looked into the topic, specifically researching the context manager and found this which gives slightly more detail for those interested.
class GeneratorContextManager(object):
def __init__(self, gen):
# Store local copy of "file reference"
self.gen = gen
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
return
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
# only re-raise if it's *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But
# throw() has to raise the exception to signal
# propagation, so this fixes the impedance mismatch
# between the throw() protocol and the __exit__()
# protocol.
#
if sys.exc_info()[1] is not value:
raise
def contextmanager(func):
def helper(*args, **kwds):
return GeneratorContextManager(func(*args, **kwds))
return helper