Skip to content

Commit 8a9027c

Browse files
authored
address utcnow deprecation (#91)
1 parent 7a1e5a9 commit 8a9027c

10 files changed

Lines changed: 70 additions & 24 deletions

File tree

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
strategy:
1111
matrix:
1212
os: [ubuntu-latest]
13-
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
13+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
1414
runs-on: ${{ matrix.os }}
1515
steps:
1616
- uses: actions/checkout@v2

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ DEPOT - File Storage Made Easy
1414
:target: https://pypi.python.org/pypi/filedepot
1515

1616
DEPOT is a framework for easily storing and serving files in
17-
web applications on Python2.6+ and Python3.2+.
17+
web applications on Python 3.9+.
1818

1919
DEPOT supports storing files in multiple backends, like:
2020

depot/_compat.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import platform, sys
2+
from datetime import datetime, timedelta, tzinfo
23

34

45
if platform.system() == 'Windows': # pragma: no cover
@@ -55,3 +56,25 @@ def percent_decode(string):
5556
def with_metaclass(meta, base=object):
5657
"""Create a base class with a metaclass."""
5758
return meta("NewBase", (base,), {})
59+
60+
61+
class _UTC(tzinfo):
62+
def utcoffset(self, dt):
63+
return timedelta(0)
64+
65+
def tzname(self, dt):
66+
return "UTC"
67+
68+
def dst(self, dt):
69+
return timedelta(0)
70+
71+
72+
_UTC_TZINFO = _UTC()
73+
74+
75+
def utcnow_naive():
76+
return datetime.now(_UTC_TZINFO).replace(tzinfo=None)
77+
78+
79+
def utcfromtimestamp_naive(ts):
80+
return datetime.fromtimestamp(ts, _UTC_TZINFO).replace(tzinfo=None)

depot/io/local.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import uuid
99
import shutil
1010
import json
11+
from io import BytesIO
1112
from datetime import datetime
1213

1314
from .interfaces import FileStorage, StoredFile
@@ -52,10 +53,11 @@ def read(self, n=-1):
5253

5354
def close(self):
5455
if self._file is None:
55-
# This is to guarantee that closing a file
56-
# before even reading it behaves correctly
57-
self._file = open(self._file_path, 'rb')
58-
self._file.close()
56+
self._file = _ClosedLocalFile(self._file_path)
57+
return
58+
59+
if not self._file.closed:
60+
self._file.close()
5961

6062
@property
6163
def closed(self):
@@ -64,6 +66,13 @@ def closed(self):
6466
return self._file.closed
6567

6668

69+
class _ClosedLocalFile(BytesIO):
70+
def __init__(self, file_path):
71+
super(_ClosedLocalFile, self).__init__(b'')
72+
self.name = file_path
73+
self.close()
74+
75+
6776
class LocalFileStorage(FileStorage):
6877
""":class:`depot.io.interfaces.FileStorage` implementation that stores files locally.
6978

depot/io/utils.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import mimetypes
22
import os
3-
from datetime import datetime
43
from tempfile import SpooledTemporaryFile
5-
from depot._compat import byte_string
4+
from depot._compat import byte_string, utcnow_naive
65

76

87
INMEMORY_FILESIZE = 1024*1024
98

109

1110
def timestamp():
12-
return datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
11+
return utcnow_naive().strftime('%Y-%m-%d %H:%M:%S')
1312

1413

1514
def file_from_content(content):

depot/middleware.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from email.utils import parsedate_tz, mktime_tz
33
from time import gmtime, time
44
from .manager import DepotManager
5-
from ._compat import wsgi_string
5+
from ._compat import wsgi_string, utcfromtimestamp_naive
66
from .utils import make_content_disposition
77

88
_BLOCK_SIZE = 4096 * 64 # 256K
@@ -47,7 +47,7 @@ def generate_etag(self):
4747

4848
def parse_date(self, value):
4949
try:
50-
return datetime.utcfromtimestamp(mktime_tz(parsedate_tz(value)))
50+
return utcfromtimestamp_naive(mktime_tz(parsedate_tz(value)))
5151
except (TypeError, OverflowError):
5252
raise RuntimeError("Received an ill-formed timestamp")
5353

setup.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@
4343
"Environment :: Web Environment",
4444
"Topic :: Software Development :: Libraries :: Python Modules",
4545
"Programming Language :: Python :: 3",
46-
"Programming Language :: Python :: 2"
46+
"Programming Language :: Python :: 3.9",
47+
"Programming Language :: Python :: 3.10",
48+
"Programming Language :: Python :: 3.11",
49+
"Programming Language :: Python :: 3.12",
50+
"Programming Language :: Python :: 3.13",
51+
"Programming Language :: Python :: 3.14"
4752
],
4853
keywords='storage files s3 gridfs mongodb aws sqlalchemy wsgi',
4954
author='Alessandro Molina',
@@ -52,6 +57,7 @@
5257
license='MIT',
5358
packages=find_packages(exclude=['ez_setup', 'tests']),
5459
include_package_data=True,
60+
python_requires='>=3.9',
5561
install_requires=INSTALL_DEPENDENCIES,
5662
tests_require=TEST_DEPENDENCIES,
5763
extras_require={

tests/base_sqla.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from datetime import datetime
21
from sqlalchemy import *
32
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
43
from sqlalchemy.orm import declarative_base
4+
from depot._compat import utcnow_naive
55

66
maker = sessionmaker(autoflush=True, autocommit=False)
77
DBSession = scoped_session(maker)
@@ -42,7 +42,7 @@ class ThingWithDate(DeclarativeBase):
4242

4343
uid = Column(Integer, autoincrement=True, primary_key=True)
4444
name = Column(Unicode(16), unique=True)
45-
updated_at = Column(DateTime, default=datetime.utcnow)
45+
updated_at = Column(DateTime, default=utcnow_naive)
4646

4747
related_thing_id = Column(Integer, ForeignKey('thing.uid'))
4848
related_thing = relationship(Thing)

tests/test_fields_basic.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
import shutil
33
import tempfile, os, base64
44
import unittest
5+
import datetime
56
from sqlalchemy.schema import Column
67
from sqlalchemy.types import Unicode, Integer
7-
from .base_sqla import setup_database, clear_database, DeclarativeBase, DBSession
8+
from .base_sqla import setup_database, clear_database, DeclarativeBase, DBSession, ThingWithDate
89
from .utils import OpenFiles
910
from depot.fields.sqlalchemy import UploadedFileField
1011
from depot.fields.upload import UploadedFile
1112
from depot.manager import DepotManager
1213
from depot.fields.interfaces import FileFilter
14+
from depot.io import utils as io_utils
1315

1416
from depot._compat import u_, bytes_
1517

@@ -134,3 +136,16 @@ def test_storage_does_not_exists(self):
134136
DBSession.add(doc)
135137
DBSession.flush()
136138
DBSession.commit()
139+
140+
def test_thing_with_date_default_is_naive(self):
141+
doc = ThingWithDate(name=u_('WithDate'))
142+
DBSession.add(doc)
143+
DBSession.flush()
144+
DBSession.commit()
145+
146+
assert doc.updated_at is not None
147+
assert doc.updated_at.tzinfo is None
148+
149+
def test_timestamp_is_naive_utc_compatible_string(self):
150+
value = datetime.datetime.strptime(io_utils.timestamp(), '%Y-%m-%d %H:%M:%S')
151+
assert value.tzinfo is None

tox.ini

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,12 @@
66
[tox]
77
skip_missing_interpreters = True
88
envlist =
9-
py26
10-
py27
11-
py33
12-
py34
13-
py35
14-
py36
15-
py37
16-
py38
179
py39
1810
py310
1911
py311
12+
py312
13+
py313
14+
py314
2015

2116
[testenv]
2217
setenv =
@@ -33,4 +28,3 @@ commands =
3328
changedir = {toxinidir}/docs
3429
commands = sphinx-build -b html -d _build/doctrees . _build/html
3530
deps = -rdocs/requirements.txt
36-

0 commit comments

Comments
 (0)