1
0
mirror of https://github.com/jbranchaud/til synced 2026-07-06 17:20:33 +00:00

Compare commits

...

2 Commits

Author SHA1 Message Date
jbranchaud 950e2f861a Add Define Conditional Routing Logic In Routes File as a Rails TIL 2026-06-22 17:52:13 -05:00
jbranchaud 20bbdb6d55 Add Make Secure Temp File For Atomic Write as a Python TIL 2026-06-22 17:26:14 -05:00
3 changed files with 88 additions and 1 deletions
+3 -1
View File
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1798 TILs and counting..._ _1800 TILs and counting..._
See some of the other learning resources I work on: See some of the other learning resources I work on:
@@ -1079,6 +1079,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Load A File Into The Python REPL](python/load-a-file-into-the-python-repl.md) - [Load A File Into The Python REPL](python/load-a-file-into-the-python-repl.md)
- [Look Inside Pytest tmp_path](python/look-inside-pytest-tmp-path.md) - [Look Inside Pytest tmp_path](python/look-inside-pytest-tmp-path.md)
- [Make Dataclass Sortable By Specific Field](python/make-dataclass-sortable-by-specific-field.md) - [Make Dataclass Sortable By Specific Field](python/make-dataclass-sortable-by-specific-field.md)
- [Make Secure Temp File For Atomic Write](python/make-secure-temp-file-for-atomic-write.md)
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md) - [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md)
- [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md) - [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md)
- [Reclassify Certain Packages As Dev Dependencies](python/reclassify-certain-packages-as-dev-dependencies.md) - [Reclassify Certain Packages As Dev Dependencies](python/reclassify-certain-packages-as-dev-dependencies.md)
@@ -1147,6 +1148,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Customize Paths And Helpers For Devise Routes](rails/customize-paths-and-helpers-for-devise-routes.md) - [Customize Paths And Helpers For Devise Routes](rails/customize-paths-and-helpers-for-devise-routes.md)
- [Customize Template For New Schema Migration](rails/customize-template-for-new-schema-migration.md) - [Customize Template For New Schema Migration](rails/customize-template-for-new-schema-migration.md)
- [Customize The Path Of A Resource Route](rails/customize-the-path-of-a-resource-route.md) - [Customize The Path Of A Resource Route](rails/customize-the-path-of-a-resource-route.md)
- [Define Conditional Routing Logic In Routes File](rails/define-conditional-routing-logic-in-routes-file.md)
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md) - [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
- [Delete Paranoid Records](rails/delete-paranoid-records.md) - [Delete Paranoid Records](rails/delete-paranoid-records.md)
- [Demodulize A Class Name](rails/demodulize-a-class-name.md) - [Demodulize A Class Name](rails/demodulize-a-class-name.md)
@@ -0,0 +1,48 @@
# Make Secure Temp File For Atomic Write
Two types of failure modes that can occur while writing to a shared file on the
file system are 1) a corrupted file due to a crash mid-write and 2) another
process reading a partial file mid-write.
One way I've handled this in [`py-vmt`](https://github.com/jbranchaud/py-vmt) is
to perform the write operations on a secure temp file and then use the OS-level
atomic `rename` operation. I do this by [creating a
`contextmanager`](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager)
that uses
[`tempfile.mkstemp`](https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp)
and [`os.replace`](https://docs.python.org/3/library/os.html#os.replace).
Here is what the `contextmanager` looks like:
```python
from contextlib import contextmanager
from pathlib import Path
import os, tempfile
@contextmanager
def atomic_write(path: Path):
# write to a tmp file in the same directory, then atomically swap it
fd, temp_file_path = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
try:
with os.fdopen(fd, "w") as file:
yield file
os.replace(temp_file_path, path)
except BaseException:
os.unlink(temp_file_path)
raise
```
This explicitly creates a secure temp file in the same directory as the given
path with `.tmp` as the suffix. I then open the file descriptor using the
`os.fdopen` context manager (which will manage closing the file descriptor for
me). The `@contextmanager` decorator plus the `yield file` are what allow this
to be used as a `with` block. Once any file operations are done, then I use
`os.replace` to atomically swap out the original file with the temp file.
Here is how I use it to write updates to JSON data files:
```python
def write_active_session(self, session: Session) -> None:
with atomic_write(self.active_session_file) as file:
json.dump(session.marshal(), file)
```
@@ -0,0 +1,37 @@
# Define Conditional Routing Logic In Routes File
I ran into a situation recently where I needed to intercept the behavior a
common public-facing route in an app. Broadly, the route is for company specific
rental pages with query parameters that correspond to their available inventory.
What I needed was a way to display a demo version of that rental page ignoring
everything else about how the request would otherwise be processed, validated,
and rendered.
Instead of introducing a bunch of weird conditional logic into this already
complex rental controller, I was able to intercept the request at the routing
layer when `demo=true` is set and send it to a different controller.
Here is what that section of `config/routes.rb` looks like:
```ruby
get "rentals/new", to: "rental_demos#show",
as: :rental_demo,
constraints: ->(request) { request.params[:demo] == "true" }
resources :rentals, only: %i[new create] do
# ...
end
```
This specifies a `constraint` on the `get` handler matching for a given request.
If the constraint isn't met, then the route handling logic proceeds where it
will instead find a match with the original new rentals resource routing.
Now I can reference a version of this URL that includes `demo=true` as a way of
having an always-available realistic-looking version of the rental page even if
one of these companies doesn't actively have available inventory.
Those requests will get intercepted by the first matching route handler which
will send them to the `RentalDemosController` instead of the
`RentalsController`.