85 lines
2.6 KiB
Markdown
85 lines
2.6 KiB
Markdown
![]() |
---
|
||
|
tags: [python]
|
||
|
created: Sunday, March 16, 2025
|
||
|
---
|
||
|
|
||
|
# Using a context manager in Python
|
||
|
|
||
|
For certain process that have a start and an end state such as opening and
|
||
|
closing a file or connecting and disconnecting from a database, instead of
|
||
|
having dedicated open/close, connect/disconnect handling, you can use a Context
|
||
|
Manager as a form of "syntactic sugar".
|
||
|
|
||
|
By using a Context Manager you can use a more Pythonic construction of e.g:
|
||
|
|
||
|
```py
|
||
|
with open('/file-path') as file:
|
||
|
... # handle file operation
|
||
|
```
|
||
|
|
||
|
```py
|
||
|
with open DatabaseService() as connection:
|
||
|
... # do database stuff
|
||
|
```
|
||
|
|
||
|
When you use this approach it is an abstraction over `try...finally`. Meaning
|
||
|
the clean-up `finally` operation will run automatically without you having to
|
||
|
explicitly handle it.
|
||
|
|
||
|
Some common processes such as file handling have the Context Manager built-in
|
||
|
and you don't have to explicitly provision it.
|
||
|
|
||
|
Other processes may lend themselves to `with` syntax but you have to do the
|
||
|
configuration yourself.
|
||
|
|
||
|
One such example is creating a database accessor. Below I have done this by
|
||
|
using `__enter__` and `__exit__` methods on a database class:
|
||
|
|
||
|
```py
|
||
|
class DatabaseService:
|
||
|
def __init__(self, db_name, db_path):
|
||
|
self.db_name = db_name
|
||
|
self.db_path = db_path
|
||
|
self.connection: Optional[sqlite3.Connection] = None
|
||
|
|
||
|
def connect(self) -> Optional[sqlite3.Connection]:
|
||
|
if self.connection is not None:
|
||
|
return self.connection
|
||
|
|
||
|
try:
|
||
|
if not os.path.exists(self.db_path):
|
||
|
os.makedirs(self.db_path)
|
||
|
print("INFO Created database directory")
|
||
|
self.connection = sqlite3.connect(f"{self.db_path}/{self.db_name}.db")
|
||
|
self.connection.execute("PRAGMA foreign_keys = ON")
|
||
|
return self.connection
|
||
|
|
||
|
except Exception as e:
|
||
|
raise Exception(f"ERROR Problem connecting to database: {e}")
|
||
|
|
||
|
def disconnect(self) -> None:
|
||
|
try:
|
||
|
if self.connection is not None:
|
||
|
self.connection.close()
|
||
|
self.connection = None
|
||
|
except Exception as e:
|
||
|
raise Exception(f"ERROR Problem disconnecting from database: {e}")
|
||
|
|
||
|
def __enter__(self) -> sqlite3.Connection:
|
||
|
connection = self.connect()
|
||
|
if connection is None:
|
||
|
raise RuntimeError("Failed to establish database connection")
|
||
|
return connection
|
||
|
|
||
|
def __exit__(self) -> None:
|
||
|
self.disconnect()
|
||
|
```
|
||
|
|
||
|
Then I can use it like so:
|
||
|
|
||
|
```py
|
||
|
with DatabaseService() as connection:
|
||
|
cursor = connection.cursor()
|
||
|
cursor.execute(...)
|
||
|
```
|