27
27
class ORMField (OrderedType ):
28
28
def __init__ (
29
29
self ,
30
- type = None ,
31
30
prop_name = None ,
31
+ type = None ,
32
+ required = None ,
32
33
description = None ,
33
34
deprecation_reason = None ,
34
- required = None ,
35
35
_creation_counter = None ,
36
36
** field_kwargs
37
37
):
38
+ """
39
+ Use this to override fields automatically generated by SQLAlchemyObjectType.
40
+ Unless specified, options will default to SQLAlchemyObjectType usual behavior
41
+ for the given SQLAlchemy model property.
42
+
43
+ Usage:
44
+ class MyModel(Base):
45
+ id = Column(Integer(), primary_key=True)
46
+ name = Column(String)
47
+
48
+ class MyType(SQLAlchemyObjectType):
49
+ class Meta:
50
+ model = MyModel
51
+
52
+ id = ORMField(type=graphene.Int)
53
+ name = ORMField(required=True)
54
+
55
+ -> MyType.id will be of type Int (vs ID).
56
+ -> MyType.name will be of type NonNull(String) (vs String).
57
+
58
+ Parameters
59
+ - prop_name : str, optional
60
+ Name of the SQLAlchemy property used to resolve this field.
61
+ Default to the name of the attribute referencing the ORMField.
62
+ - type: optional
63
+ Default to the type mapping in converter.py.
64
+ - description: str, optional
65
+ Default to the `doc` attribute of the SQLAlchemy column property.
66
+ - required: bool, optional
67
+ Default to the opposite of the `nullable` attribute of the SQLAlchemy column property.
68
+ - description: str, optional
69
+ Same behavior as in graphene.Field. Defaults to None.
70
+ - deprecation_reason: str, optional
71
+ Same behavior as in graphene.Field. Defaults to None.
72
+ - _creation_counter: int, optional
73
+ Same behavior as in graphene.Field.
74
+ """
38
75
super (ORMField , self ).__init__ (_creation_counter = _creation_counter )
39
76
# The is only useful for documentation and auto-completion
40
77
common_kwargs = {
41
- 'type ' : type ,
42
- 'prop_name ' : prop_name ,
43
- 'description ' : description ,
44
- 'deprecation_reason ' : deprecation_reason ,
45
- 'required ' : required ,
78
+ 'prop_name ' : prop_name ,
79
+ 'type ' : type ,
80
+ 'required ' : required ,
81
+ 'description ' : description ,
82
+ 'deprecation_reason ' : deprecation_reason ,
46
83
}
47
84
common_kwargs = {kwarg : value for kwarg , value in common_kwargs .items () if value is not None }
48
85
self .kwargs = field_kwargs
@@ -52,7 +89,27 @@ def __init__(
52
89
def construct_fields (
53
90
obj_type , model , registry , only_fields , exclude_fields , connection_field_factory
54
91
):
92
+ """
93
+ Construct all the fields for a SQLAlchemyObjectType.
94
+ The main steps are:
95
+ - Gather all the relevant attributes from the SQLAlchemy model
96
+ - Gather all the ORM fields defined on the type
97
+ - Merge in overrides and build up all the fields
98
+
99
+ Parameters
100
+ - obj_type : SQLAlchemyObjectType
101
+ - model : the SQLAlchemy model
102
+ - registry : Registry
103
+ - only_fields : tuple[string]
104
+ - exclude_fields : tuple[string]
105
+ - connection_field_factory : function
106
+
107
+ Returns
108
+ - fields
109
+ An OrderedDict of field names to graphene.Field
110
+ """
55
111
inspected_model = sqlalchemyinspect (model )
112
+ # Gather all the relevant attributes from the SQLAlchemy model
56
113
all_model_props = OrderedDict (
57
114
inspected_model .column_attrs .items () +
58
115
inspected_model .composites .items () +
@@ -61,31 +118,37 @@ def construct_fields(
61
118
inspected_model .relationships .items ()
62
119
)
63
120
121
+ # Filter out excluded fields
64
122
auto_orm_field_names = []
65
123
for prop_name , prop in all_model_props .items ():
66
124
if (only_fields and prop_name not in only_fields ) or (prop_name in exclude_fields ):
67
125
continue
68
126
auto_orm_field_names .append (prop_name )
69
127
70
- # TODO Get ORMField fields defined on parent classes
71
- custom_orm_fields_items = []
72
- for attname , value in list (obj_type .__dict__ .items ()):
73
- if isinstance (value , ORMField ):
74
- custom_orm_fields_items .append ((attname , value ))
128
+ # Gather all the ORM fields defined on the type
129
+ custom_orm_fields_items = [
130
+ (attname , value )
131
+ for base in reversed (obj_type .__mro__ )
132
+ for attname , value in base .__dict__ .items ()
133
+ if isinstance (value , ORMField )
134
+ ]
75
135
custom_orm_fields_items = sorted (custom_orm_fields_items , key = lambda item : item [1 ])
76
136
137
+ # Set the prop_name if not set
77
138
for orm_field_name , orm_field in custom_orm_fields_items :
78
139
prop_name = orm_field .kwargs .get ('prop_name' , orm_field_name )
79
140
if prop_name not in all_model_props :
80
141
raise Exception ('Cannot map ORMField "{}" to SQLAlchemy model property' .format (orm_field_name ))
81
142
orm_field .kwargs ['prop_name' ] = prop_name
82
143
144
+ # Merge automatic fields with custom ORM fields
83
145
orm_fields = OrderedDict (custom_orm_fields_items )
84
146
for orm_field_name in auto_orm_field_names :
85
147
if orm_field_name in orm_fields :
86
148
continue
87
149
orm_fields [orm_field_name ] = ORMField (prop_name = orm_field_name )
88
150
151
+ # Build all the field dictionary
89
152
fields = OrderedDict ()
90
153
for orm_field_name , orm_field in orm_fields .items ():
91
154
prop_name = orm_field .kwargs .pop ('prop_name' )
0 commit comments