
Originally published onthomaseckert.dev
In my latest project, I've added Tailwind and Vue to a Flask app. This requires an additional build step to compile each usingnpm
during deployment. Given that Python is already present on the server by the time the build step occurs, I decided to write the build script in Python.
Those who have written build scripts before may be familiar with the pattern of changing into a directory, executing commands, then returning to the original directory to start the next set of commands. That was exactly what I needed to do here:
- Change directory to
./tailwind
- Execute
npm install
to install dependencies - Execute
npm run build
to compile the Tailwind CSS
- Execute
- Change directory
..
- Change directory
./vue
- Execute
npm install
to install dependencies - Execute
npm run build
to compile the Vue application
- Execute
This seemed like a perfect fit for a Python context manager. Context managers allow for the instatiation of a context using thewith
keyword. The context is disposed when the code is dedented. Using a context manager to change directories here would eliminate the relative path directory change in step 2, once the first step is completed, the directory would be automatically reset to what it was before the context was initiated.
By writing the right context managerset_directory
, I could implement the build script as
frompathlibimportPathwithset_directory(Path("./tailwind")):run_npm_install()run_npm_build()withset_directory(Path("./vue")):run_npm_install()run_npm_build()
and I thought that was pretty slick!
Context managers can be written as classes or functions. Given the relative simplicity of this context, I opted to use a function. A context manager function must be decorated with@contextmanager
which is imported fromcontextlib
. It should have atry
block with ayield
and afinally
block. When the context is instantiated using thewith
keyword, thetry
block is run. When the indented code block is left, thefinally
block is run. As an example,
fromcontextlibimportcontextmanager@contextmanagerdeffriendly_context():try:print("Hello! Welcome to the context!")yieldfinally:print("Bye now. Thank you for visiting the context. Come again soon.")withfriendly_context():print("Oh thank you, it is so nice to be in the context.")
when executed will print
Hello! Welcome to the context!Oh thank you, it is so nice to be in the context.Bye now. Thank you for visiting the context. Come again soon.
To write my directory changing context manager, I needed to save the original path to a variable, change it in thetry
block to whatever was passed in to the function, then change to the original path in thefinally
block.
fromcontextlibimportcontextmanagerfrompathlibimportPathimportos@contextmanagerdefset_directory(path:Path):"""Sets the cwd within the context Args: path (Path): The path to the cwd Yields: None """origin=Path().absolute()try:os.chdir(path)yieldfinally:os.chdir(origin)
And it works like a charm! Let me know if you found a cool use for context managers or would have solved this problem a different way.
Top comments(3)

- LocationPeoria, Illinois
- WorkPython Dev
- Joined
Great use case for a context manager. Outside of opening files context managers are far too underutilized.
There is also an alternativeclass
based syntax that you may run into on occasion.
classset_directory(object):"""Sets the cwd within the context Args: path (Path): The path to the cwd """def__init__(self,path:Path):self.path=pathself.origin=Path().absolute()def__enter__(self):os.chdir(self.path)def__exit__(self):os.chdir(self.origin)

- LocationSeattle
- EducationMasters in Physics
- WorkSoftware Engineer at Microsoft
- Joined
Great point! Thank you for providing a full class example. I'm sure people will find it useful to reference!
For further actions, you may consider blocking this person and/orreporting abuse