11from __future__ import annotations
22
3- import operator
43from collections .abc import Container
5- from typing import Any
4+ from typing import TYPE_CHECKING , Any , Generic , Self , TypeVar , overload
65from uuid import uuid4
76
8- from django .db .models import F , Field , Model
9- from django .db .models .expressions import BaseExpression , CombinedExpression , Value
7+ from django .db .models import Field , Model
108from django .utils .crypto import get_random_string
119from django .utils .text import slugify
1210
13- from sentry .db .exceptions import CannotResolveExpression
14-
15- COMBINED_EXPRESSION_CALLBACKS = {
16- CombinedExpression .ADD : operator .add ,
17- CombinedExpression .SUB : operator .sub ,
18- CombinedExpression .MUL : operator .mul ,
19- CombinedExpression .DIV : operator .floordiv ,
20- CombinedExpression .MOD : operator .mod ,
21- CombinedExpression .BITAND : operator .and_ ,
22- CombinedExpression .BITOR : operator .or_ ,
23- }
24-
25-
26- def resolve_combined_expression (instance : Model , node : BaseExpression ) -> BaseExpression :
27- def _resolve (instance : Model , node : BaseExpression | F ) -> BaseExpression :
28- if isinstance (node , Value ):
29- return node .value
30- if isinstance (node , F ):
31- return getattr (instance , node .name )
32- if isinstance (node , CombinedExpression ):
33- return resolve_combined_expression (instance , node )
34- return node
35-
36- if isinstance (node , Value ):
37- return node .value
38- if not hasattr (node , "connector" ):
39- raise CannotResolveExpression
40- op = COMBINED_EXPRESSION_CALLBACKS .get (node .connector , None )
41- if not op :
42- raise CannotResolveExpression
43- if hasattr (node , "children" ):
44- children = node .children
45- else :
46- children = [node .lhs , node .rhs ]
47- runner = _resolve (instance , children [0 ])
48- for n in children [1 :]:
49- runner = op (runner , _resolve (instance , n ))
50- return runner
11+ if TYPE_CHECKING :
12+ from sentry .db .models .base import Model as SentryModel
5113
5214
5315def unique_db_instance (
54- inst : Model ,
16+ inst : SentryModel ,
5517 base_value : str ,
5618 reserved : Container [str ] = (),
5719 max_length : int = 30 ,
@@ -62,7 +24,7 @@ def unique_db_instance(
6224 if base_value is not None :
6325 base_value = base_value .strip ()
6426 if base_value in reserved :
65- base_value = None
27+ base_value = ""
6628
6729 if not base_value :
6830 base_value = uuid4 ().hex [:12 ]
@@ -105,7 +67,7 @@ def unique_db_instance(
10567
10668
10769def slugify_instance (
108- inst : Model ,
70+ inst : SentryModel ,
10971 label : str ,
11072 reserved : Container [str ] = (),
11173 max_length : int = 30 ,
@@ -119,20 +81,33 @@ def slugify_instance(
11981 return unique_db_instance (inst , value , reserved , max_length , field_name , * args , ** kwargs )
12082
12183
122- class Creator :
84+ # matches django-stubs for Field
85+ _ST = TypeVar ("_ST" , contravariant = True )
86+ _GT = TypeVar ("_GT" , covariant = True )
87+
88+
89+ class Creator (Generic [_ST , _GT ]):
12390 """
12491 A descriptor that invokes `to_python` when attributes are set.
12592 This provides backwards compatibility for fields that used to use
12693 SubfieldBase which will be removed in Django1.10
12794 """
12895
129- def __init__ (self , field : Field ) :
96+ def __init__ (self , field : Field [ _ST , _GT ]) -> None :
13097 self .field = field
13198
132- def __get__ (self , obj : Model , type : Any = None ) -> Any :
133- if obj is None :
99+ @overload
100+ def __get__ (self , inst : Model , owner : type [Any ]) -> Any :
101+ ...
102+
103+ @overload
104+ def __get__ (self , inst : None , owner : type [Any ]) -> Self :
105+ ...
106+
107+ def __get__ (self , inst : Model | None , owner : type [Any ]) -> Self | Any :
108+ if inst is None :
134109 return self
135- return obj .__dict__ [self .field .name ]
110+ return inst .__dict__ [self .field .name ]
136111
137112 def __set__ (self , obj : Model , value : Any ) -> None :
138113 obj .__dict__ [self .field .name ] = self .field .to_python (value )
0 commit comments