Skip to content

Commit 81956a3

Browse files
authored
Merge pull request #330 from girder/registration
2 parents 5e04bc0 + 5e09013 commit 81956a3

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

s3_file_field/_registry.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import TYPE_CHECKING, Iterator
2+
import warnings
23
from weakref import WeakValueDictionary
34

45
from django.core.files.storage import Storage
@@ -17,9 +18,15 @@
1718

1819
def register_field(field: "S3FileField") -> None:
1920
field_id = field.id
20-
if field_id in _fields and not (_fields[field_id] is field):
21+
if field_id in _fields and _fields[field_id] is not field:
2122
# This might be called multiple times, but it should always be consistent
22-
raise RuntimeError(f"Cannot overwrite existing S3FileField declaration for {field_id}")
23+
warnings.warn(
24+
f"Overwriting existing S3FileField declaration for {field_id}",
25+
RuntimeWarning,
26+
# This should attribute to the re-defining class (instead of S3FF or Django internals),
27+
# but it was determined empirically and could break if Django is restructured.
28+
stacklevel=5,
29+
)
2330
_fields[field_id] = field
2431

2532
storage = field.storage

tests/test_registry.py

+44-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
from typing import cast
1+
import inspect
2+
import sys
3+
from typing import Generator, cast
24

35
from django.core.files.storage import default_storage
6+
from django.db import models
47
import pytest
58

69
from s3_file_field import _registry
@@ -15,6 +18,21 @@ def s3ff_field() -> S3FileField:
1518
return cast(S3FileField, Resource._meta.get_field("blob"))
1619

1720

21+
@pytest.fixture()
22+
def ephemeral_s3ff_field() -> Generator[S3FileField, None, None]:
23+
# Declaring this will implicitly register the field
24+
class EphemeralResource(models.Model):
25+
class Meta:
26+
app_label = "test_app"
27+
28+
blob = S3FileField()
29+
30+
field = cast(S3FileField, EphemeralResource._meta.get_field("blob"))
31+
yield field
32+
# The registry state is global to the process, so attempt to clean up
33+
del _registry._fields[field.id]
34+
35+
1836
def test_field_id(s3ff_field: S3FileField) -> None:
1937
assert s3ff_field.id == "test_app.Resource.blob"
2038

@@ -41,3 +59,28 @@ def test_registry_iter_storages() -> None:
4159

4260
assert len(fields) == 1
4361
assert fields[0] is default_storage
62+
63+
64+
@pytest.mark.skipif(sys.version_info < (3, 9), reason="Bug bpo-35113")
65+
def test_registry_register_field_multiple(ephemeral_s3ff_field: S3FileField) -> None:
66+
with pytest.warns(
67+
RuntimeWarning, match=r"Overwriting existing S3FileField declaration"
68+
) as records:
69+
# Try to create a field with the same id
70+
class EphemeralResource(models.Model):
71+
class Meta:
72+
app_label = "test_app"
73+
74+
blob = S3FileField()
75+
76+
# Ensure the warning is attributed to the re-defined model, since stacklevel is set empirically
77+
warning = next(record for record in records if str(record.message).startswith("Overwriting"))
78+
assert warning.filename == inspect.getsourcefile(EphemeralResource)
79+
assert warning.lineno == inspect.getsourcelines(EphemeralResource)[1]
80+
81+
duplicate_field = cast(S3FileField, EphemeralResource._meta.get_field("blob"))
82+
# Sanity check
83+
assert duplicate_field.id == ephemeral_s3ff_field.id
84+
assert duplicate_field is not ephemeral_s3ff_field
85+
# The most recently registered field should by stored
86+
assert _registry.get_field(duplicate_field.id) is duplicate_field

0 commit comments

Comments
 (0)