From dd58946c3c25c67b6e7cbc94887bc224c9eabeca Mon Sep 17 00:00:00 2001 From: "J. M. F. Tsang" Date: Sat, 2 Sep 2023 14:53:30 +0100 Subject: [PATCH] Use a cache directory instead of temporary files Temporary files behave differently on Windows from how they do on Unix: https://stackoverflow.com/a/23212515 which was causing permission errors when creating and writing docx files. To avoid this, we will use a regular cache directory. However, note that files no longer get cleaned up automatically (issue #27). --- requirements.txt | 1 + tests/test_pypew.py | 9 +++++++-- utils.py | 5 +++++ views/feast_views.py | 16 +++++++++------- views/pew_sheet_views.py | 17 ++++++++--------- 5 files changed, 30 insertions(+), 18 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4d07fbb..1e4cae8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +appdirs~=1.4.4 attrs~=21.2.0 cattrs~=22.1.0 docxtpl~=0.15.2 diff --git a/tests/test_pypew.py b/tests/test_pypew.py index cdd50a5..4311c78 100644 --- a/tests/test_pypew.py +++ b/tests/test_pypew.py @@ -1,5 +1,6 @@ import unittest from datetime import date +from pathlib import Path from unittest.mock import patch from urllib.parse import urlencode @@ -15,6 +16,10 @@ from utils import advent +def m_create_docx_impl(path): + Path(path).touch() + + class TestDates(unittest.TestCase): @parameterized.expand([ # Christmas Day in 2021 was a Saturday @@ -159,7 +164,7 @@ def test_feast_detail_view_handles_not_found(self): ) self.assertEqual(r.status_code, 404) - @patch('pypew.views.feast_views.Feast.create_docx') + @patch('pypew.views.feast_views.Feast.create_docx', side_effect=m_create_docx_impl) def test_feast_docx_view(self, m_create_docx): r = self.client.get( url_for('feast_docx_view', slug='christmas-day') @@ -175,7 +180,7 @@ def test_feast_docx_view(self, m_create_docx): r.headers['Content-Type'], ) - @patch('pypew.views.pew_sheet_views.Service.create_docx') + @patch('pypew.views.pew_sheet_views.Service.create_docx', side_effect=m_create_docx_impl) def test_pew_sheet_docx_view(self, m_create_docx): r = self.client.get( url_for('pew_sheet_docx_view') + '?' + urlencode( diff --git a/utils.py b/utils.py index 1c7b8d8..83ffa15 100644 --- a/utils.py +++ b/utils.py @@ -4,6 +4,8 @@ from functools import lru_cache from typing import Optional +from appdirs import AppDirs + try: import pandas as pd except ImportError: @@ -14,6 +16,9 @@ class NoPandasError(RuntimeError): pass +cache_dir = AppDirs("pypew").user_cache_dir +os.makedirs(cache_dir, exist_ok=True) + logger = logging.getLogger("pypew") logger.setLevel(logging.INFO) diff --git a/views/feast_views.py b/views/feast_views.py index 9962e39..7e2ba09 100644 --- a/views/feast_views.py +++ b/views/feast_views.py @@ -1,5 +1,7 @@ import datetime -from tempfile import NamedTemporaryFile +import os +import uuid +from tempfile import TemporaryDirectory import cattrs from flask import (flash, make_response, render_template, send_file, @@ -8,7 +10,7 @@ from filters import english_date from models import Feast from models_base import NotFoundError, get -from utils import str2date +from utils import str2date, cache_dir __all__ = ['feast_index_view', 'feast_index_api', 'feast_date_api', 'feast_upcoming_api', 'feast_detail_view', 'feast_detail_api', @@ -89,8 +91,8 @@ def feast_docx_view(slug): return make_response(feast_index_view(), 404) filename = f'{feast.name}.docx' - with NamedTemporaryFile() as tf: - feast.create_docx(path=tf.name) - return send_file( - tf.name, as_attachment=True, attachment_filename=filename - ) + temp_docx = os.path.join(cache_dir, f"feast_{str(uuid.uuid4())}.docx") + feast.create_docx(path=temp_docx) + return send_file( + temp_docx, as_attachment=True, attachment_filename=filename + ) diff --git a/views/pew_sheet_views.py b/views/pew_sheet_views.py index 57bc570..fc62b83 100644 --- a/views/pew_sheet_views.py +++ b/views/pew_sheet_views.py @@ -1,5 +1,5 @@ import os -from tempfile import NamedTemporaryFile +import uuid from urllib.parse import parse_qs, urlencode import dotenv @@ -9,7 +9,7 @@ from forms import PewSheetForm from models import Service, Feast -from utils import logger +from utils import logger, cache_dir __all__ = ['pew_sheet_create_view', 'pew_sheet_clear_history_endpoint', 'pew_sheet_docx_view'] @@ -79,10 +79,9 @@ def pew_sheet_docx_view(): datestamp = service.date.strftime("%Y-%m-%d") - with NamedTemporaryFile() as tf: - service.create_docx(tf.name) - return send_file( - tf.name, - as_attachment=True, - attachment_filename=f'{datestamp} {service.title}.docx' - ) + filename = f'{datestamp} {service.title}.docx' + temp_docx = os.path.join(cache_dir, f"pew_sheet_{str(uuid.uuid4())}.docx") + service.create_docx(temp_docx) + return send_file( + temp_docx, as_attachment=True, attachment_filename=filename + )