Summary
What's Context Manager?
A context manager is a concept that enables us to perform predefined actions both before and after our main action. We use them using with keyword.
You've probably used context managers before for opening files like that👇.
with open("example.txt", "r") as file:
content = file.read()
This open context-manager ensures that file is closed after the code block. This is crucial to not corrupt files when you work in the 'write' mode with any files.
We can create similar context-manager, that will Start and Commit our Transaction. And on top of that we will add try/except statements and optionally print error message.
How to Create Context Manager?
There are 2 ways to create a context manager.
1️⃣ We can create a generator function and apply a special decorator for that function, so python recognizes it as a context manager.
2️⃣ Or we can create a Class and use its __enter__ and __exit__ methods.
💡 I prefer using functions if context manager is simple enough, and use Class if I need a lot of functionality.
Let's look how to create them.
Create Context Manager with Function
To create context manager with a function we need to import contextlib module and use context-manager decorator. This will indicate python that our generator function will be used as a context-manager.
💡Generator functions use yield instead of return.
If we apply context-manager decorator, then we can define code Before and After yield statement. And our code in with statement will be placed instead of yield.
👇It might sound confusing but have a look at this template for context-manager:
import contextlib
@contextlib.contextmanager
def ef_Transaction():
print('Before')
yield
print('After')
👆 That's the base template you can use.
I only made print statements 'Before'/'After', but you can put any code instead to be executed before or after the yield part.
yield part is going to replaced with any code we write under 'with ef_Transaction:'
👇 Here is an example:
with ef_Transaction:
x = 1
y = 2
print(x+y)
So using this context-manager will give you results like the following code.
print('Before')
x = 1
y = 2
print(x+y)
print('After')
We have our print statements Before and After, and our code will be placed instead of yield statement.
Transaction Context-Manager
So in our case we would want to Create a Transaction and then Start and Commit it before and after. We also need to provide arguments for creating our transaction such as doc and title.
👇Here is simple context-manager that will do that.
import contextlib
@contextlib.contextmanager
def ef_Transaction(doc, title):
t = Transaction(doc, title)
t.Start()
yield
t.Commit()
with ef_Transaction(doc,title):
👀 Let's also add try/except statements and an argument to optionally print an error message.
import contextlib
@contextlib.contextmanager
def ef_Transaction(doc, title, debug=False):
t = Transaction(doc, title)
t.Start()
try:
yield
t.Commit()
except:
t.RollBack()
if debug:
print(traceback.format_exc())
Now this context-manager will take care of Starting and Committing a Transaction. We also have integrated try/except statement with an option to print error messages if any.
Now if we want to use it to create a random Wall, we can use the following Snippet.
from Autodesk.Revit.DB import *
doc = __revit__.ActiveUIDocument.Document
level = doc.ActiveView.GenLevel
with ef_Transaction(doc, 'Create a Wall'):
start = XYZ(0, 0, 0)
end = XYZ(10, 10, 0)
geomLine = Line.CreateBound(start, end)
new_wall = Wall.Create(doc, geomLine, level.Id, False)
By using this, we don't need to use Start, Commit and Try/Except anymore. It's already included in our context-manager.
💡And you can add as much logic as you want to make it even more powerful!
Create Try/Except Context-Manager
Let's also create another context-manager to make try/except statements more compact. I saw some codes where people used more than 10+ of them, and except was everywhere just pass. So there were a lot of useless liens of code.
💡 Let's make them more readable and reusable!
We can just remove Transaction part from previous code, and we will have our try/except context-manager.
import contextlib, traceback
@contextlib.contextmanager
def try_except(debug=False):
try:
yield
except:
if debug:
print(traceback.format_exc())
So when you use it, it will look like this in comparison:
with try_except(debug=True):
with try_except(debug=True):
try:
except:
print(traceback.format_exc())
try:
except:
print(traceback.format_exc())
💡 These are only 2 statements. Imagine the difference if we would have 10+ of them.
Create Context Manager with Class
So you've learnt how to create context-manager by using generator function with a decorator. Now let's recreate the same context-manager for Transaction but using Class this time.
We would create a class as usual, and provide arguments in __init__ method.
And then we can use the following methods for making it as a context-manager.
__enter__(self)
__exit__
(self, exc_type, exc_value, traceback)
💡Don't forget to use required arguments for __exit__ method!
class ef_Transaction2:
def __init__(self, doc, title, debug=False):
self.doc = doc
self.title = title
self.debug = debug
def __enter__(self):
self.t = Transaction(self.doc, self.title)
self.t.Start()
return self.t
def __exit__(self, exc_type, exc_val, exc_tb):
try:
self.t.Commit()
except:
self.t.RollBack()
if self.debug:
print(traceback.format_exc())
👆 So now this context-manager made with a Class is exact copy of the one we made with a generator function. Give it a try.
🎯 And it's exactly the same to use it.
from Autodesk.Revit.DB import *
doc = __revit__.ActiveUIDocument.Document
level = doc.ActiveView.GenLevel
with ef_Transaction2(doc, 'Create a Wall'):
start = XYZ(0, 0, 0)
end = XYZ(10, 10, 0)
geomLine = Line.CreateBound(start, end)
new_wall = Wall.Create(doc, geomLine, level.Id, False)
Reuse your Custom Context-Manager
👀 Are you a great programmer who reuses code?
If yes, then don't forget to place one of the context-manager to your lib, so you can start using it in your future tools!
Just place the code and all necessary imports in lib/Snippets/_context_managers.py and enjoy a better way to handle Transactions.
Conclusion
Overall, it's not a complicated concept, but it might be tricky to explain it in a written format. So make sure you watch the video to better understand how it works!
It might be a little confusing if you see it for the first time. But, I think once you create and use your own custom context-manager something will click and you will suddenly understand it!
Final Code
__title__ = "05.03 - Context Manager"
__doc__ = """Date = 07.01.2023
_____________________________________________________________________
Description:
Code from lesson 05.03 - Context Manager
Example on how to create context managers in python to handle:
- Transaction
- Try/Except
_____________________________________________________________________
Author: Erik Frits"""
from Autodesk.Revit.DB import *
uidoc = __revit__.ActiveUIDocument
doc = __revit__.ActiveUIDocument.Document
level = doc.ActiveView.GenLevel
import contextlib, traceback
@contextlib.contextmanager
def ef_Transaction(doc, title, debug=False):
"""Context Manager that will initiate Transaction .Start() and .Commit() methods.
In case of an error - it will .RollBack() transaction and display error message if debug = True.
:param doc: Document where Transaction should be created
:param title: Name of Transaction
:param debug: True - Display error messages \ False - Suppres error messages."""
t = Transaction(doc, title)
t.Start()
try:
yield
t.Commit()
except:
t.RollBack()
if debug:
print(traceback.format_exc())
@contextlib.contextmanager
def try_except(debug=False):
""""""
try:
yield
except:
if debug:
print(traceback.format_exc())
with try_except():
pt = XYZ(0,0,0)
with try_except():
pt2 = XYZ(0,0,0)
with try_except():
pt3 = XYZ(0,0,0)
try:
pt = XYZ(0,0,0)
except:
print(traceback.format_exc())
try:
pt = XYZ(0,0,0)
except:
print(traceback.format_exc())
try:
pt = XYZ(0,0,0)
except:
print(traceback.format_exc())
class ef_Transaction2:
def __init__(self, doc, title, debug=False):
self.doc = doc
self.title = title
self.debug = debug
def __enter__(self):
self.t = Transaction(self.doc, self.title)
self.t.Start()
return self.t
def __exit__(self, exc_type, exc_val, exc_tb):
try:
self.t.Commit()
except:
self.t.RollBack()
if self.debug:
print(traceback.format_exc())
from Snippets._context_managers import try_except, ef_Transaction
lib/Snippets/_context_managers.py
import contextlib, traceback
from Autodesk.Revit.DB import Transaction
@contextlib.contextmanager
def ef_Transaction(doc, title, debug=False):
"""Context Manager that will initiate Transaction .Start() and .Commit() methods.
In case of an error - it will .RollBack() transaction and display error message if debug = True.
:param doc: Document where Transaction should be created
:param title: Name of Transaction
:param debug: True - Display error messages \ False - Suppres error messages.
Example:
with ef_Transaction(doc, 'title', debug = True):
#Changes Here"""
t = Transaction(doc, title)
t.Start()
try:
yield
t.Commit()
except:
t.RollBack()
if debug:
print(traceback.format_exc())
@contextlib.contextmanager
def try_except(debug=False):
"""Context Manager for using Try/Except statements.
If case of an error it will display error message if debug = True.
:param debug: True - Display error messages \ False - Suppres error messages.
Example:
with try_except(debug = True):
#Code for Try statement here."""
try:
yield
except:
if debug:
print(traceback.format_exc())
👀 There were many examples, so give some of them a try!
💡 I would also recommend to make changes and see what happens. Try to add print statements in different places, and see what you get!
✅ Don't forget to recreate it in your lib to reuse it.
Should I use Function or Class for creating Context-Manager?
Should I use Function or Class for creating Context-Manager?
Can I stick to regular way of using Transaction?
Can I stick to regular way of using Transaction?
Can Context-Manager be used for anything?
Can Context-Manager be used for anything?