mirror of
https://github.com/jbranchaud/til
synced 2026-07-06 17:20:33 +00:00
Compare commits
2 Commits
03c11f9042
...
950e2f861a
| Author | SHA1 | Date | |
|---|---|---|---|
| 950e2f861a | |||
| 20bbdb6d55 |
@@ -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`.
|
||||||
Reference in New Issue
Block a user