-
Notifications
You must be signed in to change notification settings - Fork 227
Discussion: Auto-creation of Graphene Enums #208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
@Cito I'll give you a full answer tomorrow. For now I wanted to tell you that I really appreciate you starting this design discussion. I think these sort of upfront discussions will significantly increase the overall quality of this library. You can expect me to start similar convos in the next few weeks. |
For 3), there's also the option of erroring and telling the user to provide more information. |
1. Makes sense. 1.a I agree it makes sense to use the py-enums here because the 2 I agree it makes sense. It's good that we can easily override the 3 I'm a little torn here between @Cito's and @thejcannon's suggestions. On the one hand, I don't like the idea of duplicating enums that conceptually represent the same thing since it mostly defeats the purposes of enums. I worry that people might not realize this is happening until their schemas are published and they have to introduce a backward incompatible change to fix it. On the other end, raising an error breaks the auto-magical behavior that Overall I think prefer @thejcannon's suggestion but I would love to hear what you both prefer. 4
Given that I'm ok not adding the 5 I'm in favor of having But I don't see the benefits of having
Am I missing anything? 6 If we are going to create enum functions that uses the the global registry, I think we should be consistent and do the same for other public methods of the registry. In that case, where do we want to put the public functions for
Given that those public helper functions are completely equivalent to calling the methods on the registry and that the current pattern of using 7 I agree that it makes sense to rename the values of the enum automatically and that we should have a simple way to override this behavior. That said I think the overriding mechanism should:
I realize For the purpose of this issue, I think it's fine to punt on the name conversion override for now. Thanks again for starting this discussion. |
@jnak and @thejcannon thanks for your feedback. Yes, raising errors and asking the user for more information is always a good option. However, the question here is: Where can the user provide that information? Is it necessary to touch the SA model to do that? Conceptually, I think we should assume the worst case that the user has no influence on the SA models and that these can only be imported, but not modified. So if an enum in a column doesn't have a name, we need to either deal with that case by inventing a name or provide an option to set a name on the graphene-sqlalchemy side. Regarding I also agree that we should move the creation of additional convenience functions that would use the global registry by default into a separate issue. Maybe the whole mechanism for providing the global registry should be changed, so that the global registry object always exists as a singleton that would never be None and could simply be imported and used. Then Regarding the auto-naming and the To explain what I mean let me introduce two more abbreviations: SAM = SQLAlchemy model class, SAOT = SQLAlchemyObjectType. What I realized is that we actually don't want to get an enum for a SAM column, but for a SAOT field. Similarly, we want a sort enum not for a SAM, but for a SAOT: SAOT names could be different from SAM names and SAOT field names could be different from SAM column names via #178. The prefix discussed in 3) should then be the name of the SAOT, not the name of the SAM (and would then be automatically unique), and the name of the enum itself should be derived from the SAOT field, not from the SAM column name. Note that there is a small difficulty here, because at the time of creation of the column enum, graphene-sqlalchemy only knows the SAM, not the SAOT. But it is doable anyway: We could create the enum type as a lambda expression which Graphene will evaluate only later later when the SAOT is known (Graphene already provides that mechanism for postponing type definitions). To sum up, I think we have two ways to proceed and solve 3) from above:
Personally, I prefer 2. We can and should still add an option later to override the auto-generated enum names as discussed in 1. But this could be created as another issue and postponed to a later version. Irrespective of which way we choose, I now think that instead of Does this all make sense or am I overthinking the issue? |
Just realized that contrary to |
An alternative idea just proposed by @thejcannon on the Slack channel is to get the sort enums and column enums via a method provided on the SAOT itself. Eg. |
I'm not sure we should assume that people cannot modify their SQLAlchemy models. If that's the case that means it will be very hard for them to rename enums / resolve name collisions. I'd rather assume that only the DBs cannot be changed. What would be the legitimate cases where one can import SQLAlchemy models but cannot modify anything about it?
I agree with that. I would also prefer using the actual field (vs fieldName) but there does not seem to be a clean way to do this with I would prefer |
I can imagine using models from a 3rd party package, e.g. a CMS like kotti.resources. Or you could use automap to reflect the database schema. I just tested that you can in fact create the enums from the database that way and then be sure they always reflect what's really in the database. You don't want to redefine the enums in Python in that case.
I don't think that this is convenient. For the ConnectionFields a sort argument is added if it's not an UnsortedSQLAlchemyConnectionField, so there is a difference. But for the SAOT itself no functionality is added automatically. It would just provide the |
Fair enough. That is pretty uncommon though. How about we expose the function we use to generate a g-enum tuple from an sa-enum? This way we can resolve those situations by redefining the field like this:
It's not the most succinct pattern but it is pretty straightforward and keeps our core logic simple and predictable. Given it's a pretty uncommon situation, that seems like a reasonable tradeoff.
I agree it's not the most convenient but I would prefer to keep this feature separated from SOAT like it is as the moment because there are a number of issues with that feature:
How about we just keep |
Regarding my last point, we can also have define a public |
This is what I tried to explain above and why the idea is to provide
|
Sorry I meant |
Yes, that could also pick the registry from the SAOT. But I still don't understand what would be bad about having |
To clarify, I'm in favor of I think
But I don't feel strongly about it so do whatever you think is reasonable. I just wanted to voice my concerns. I'm going to be offline for a week. I feel very good about all this so feel free to start working on a PR if you have the bandwidth. |
I have now created a new PR #210 with the implementation along the lines of what has been discussed here. When creating sort enums, you can now rescrict fields with an additional I will be busy with other projects in the next time again and leave if to you to push this forward when you are back. It would be good if the PR could be merged before other big changes are made to the master branch because it is pretty big. We can still make adaptations to it before releasing the next version. |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related topics referencing this issue. |
In PR #98 I made an attempt to improve the auto generation of Graphene Enums, but it turned out that this feature still needs some discussion. Let's clarify here what we really want to achieve first.
In the following:
The sql-enums are supported as actual data types by some databases, otherwise implemented using name constraints. Note that both g-enums and sa-enums can be based either on py-enums or on simple lists of values.
For the examples below, I use the following imports:
When g-enums are created from sa-enums by
graphene_sqlalchemy
, they must be given a GraphQL type name. We must decide how these name are generated.1) sa-enums based on py-enums
Example 1:
I think it's clear that in this case, the g-enum should be derived from
PetKind
and thus get the same namePetKind
. Since GraphQL and Python have the same conventions for type/class names, this case is all good and fine.1a) sa-enums based on py-enums, with SQL name
Example 1a:
The sa-enums can be declared with a name argument which is used for name constraints or as sql-enum type name on the database. In this case, I think the name of the py-enum should take precedence for us; the generated g-enum should still be derived from
PetKind
, and thus have the namePetKind
, notKindOfPet
.2) sa-enums based on values, with SQL name
Example 2:
Note: The name here is passed as argument to Enum, not Column. I.e. the sql-enum name is
kind_of_pet
, while the column name iskind
. This case is interesting.First, no matter which name we choose, it will not follow GraphQL type name conventions according to which type names are written in PascalCase (though I don't find it officially required or recommended, I think it's a strong convention that also resonates with the Python class name convention). So I believe an automatic name conversion should happen; the GraphQL name should be
Kind
orKindOfPet
rather thankind
orkind_of_pet
.Second, I think in this case the sql-enum name should take precedence, since it describes the enum itself and should be characteristic for the enum, while the column name only describes its usage relative to the model. Also, different column names could be used for the same enum. For example, we could have another table:
So in this case, I suggest the g-enum should be created like:
I.e. the g-enum should be named
KindOfPet
and notKind
orFavoritePetKind
. Note that in addition to the type name, the symbol names have also been converted, but this is a separate issue I will discuss below as point 7.3) sa-enums based on values, without SQL name
Example 3:
In this case, I think it would make sense to create a g-enum type named
Kind
after the column, since we have no other clue how the type should be named. However, as in the example above, the same type could be also used in a differently named column:We have two options here: First, we create another g-enum type named
FavoritePetKind
with the same values. Or we re-use theKind
type from above. But in that case it would not be clear which name to use: The first name,Kind,
or the last name,FavoritePetKind
, or the shortest name or the longest name? We would need to specify an artificial rule for picking the name. So I suggest simply creating two g-enum types with the same values, but different names, for the two columns.But we still have a problem. What if column names are equal, but values differ, like in the following
Example 3b:
My suggestion to solve this problem is to add the model name as a prefix and auto generate the g-enum types
PetKind
,PetEyeColor
,PersonKind
,PersonFavoritePetKind
andPersonEyeColor
. We could try to be smart and reuse g-enums with the same values, adding the prefix only when there are value clashes, but I think for consistency and simplicity sake we should always add the model as prefix, not only when there are conflicts.4) Should we enforce an Enum postfix?
Should we also enforce a postfix of
Enum
for g-enum names? Note that this is not a GraphQL convention, but it may make sense to avoid name clashes with other types in the GraphQL schema, particularly when we auto-create names. I.e. in the example above, the generated g-enum would bePetEyeColorEnum
instead ofPetEyeColor
. And do we want to use the postfix only for auto generated names or also add one to g-enums derived from py-enums or generated from sql-enum names? Of course, the postfix would not be added when it already exists.Currently I think we should not add such a prefix, particularly if we auto create types with model name as prefix as suggested above, since then name clashes are much less likely. And when enums already have names, they would normally also not clash with model class names. I can't think of an example where this would be a problem.
5) Retrieving g-enums
It is sometimes necessary to refer to the auto-generated g-enums, like when you are using them in GraphQL input types. In the example above, you may want to provide a query that takes a
PetKind
as argument and returns all pets of that kind. To do that, you need to refer to the g-enum generated from thepets.kind
column.I think we should provide two methods for this, one using the g-enum name as argument, another one using the column as argument. The following calls would then return the same g-enum:
The first method could also be used for retrieving sort enums for models that are generated by
graphene_sqlalchemy
to be used as argument for sorting query results. There should be also a method for getting the sort g-enum that takes the model as parameter. These should return the same sort g-enum:Should the
get_enum_for_column
andget_sort_enum_for_model
automatically create and register a g-enum when nothing has been registered yet? I think so, that's better than throwing an error. This would allow retrieving a column g-enum for usage in an argument used before the corresponding column, or a sort g-enum without explicitly creating it first using aset_sort_enum_for_model
function. Of course the latter should be provided to allow customizing the generated sort enum, but otherwise you would not even need it.6) Use the registry or utility or enum module?
To always retrieve the same g-enum that has been generated for a column or as a sort enum, the g-enums need to be stored in the
graphene_sqlalchemy
registry. The methods for retrieving will therefore naturally be methods of the Registry class. Should we also provide functions in theutility
module with the same names, that take the registry as an optional parameter, using the global registry as default? Or maybe we should put all the enum support in a separateenum
module instead? The functions for creating sort enums could then also live there. I think that makes sense.7) Values of g-enums
GraphQL has the convention (recommended in the specs) that enum values are named like
ENUM_VALUE
instead ofenum_value
orEnumValue
. On the database, no such convention exists, and sql-enums often use lowercase names. Python enums also often use uppercase member names, but this is not really a convention, and they are also often lowercase.So the question is: Should we alter the names of enum values to be uppercase in the g-enum? I think so. Graphene also adapts the names of fields from Python to GraphQL already. Graphene does not rename the enum values, and leaves it up to you to follow the conventions, which I think makes sense. However, in the case of
graphene_sqlalchemy
we cannot define the names as we like, but must take those from the database. So I think in that case it makes sense to transform the names of g-enum values from the database to Graphene so that they follow GraphQL conventions. The values of the g-enum values should not be transformed. See the example above:For those who really don't want to transform enum value names, we could support a registry attribute
convert_enum_value_names: bool
that would be True by default but could be set to False to deactivate the automatic conversion.Please let me hear your opinions, particulary if you have different opinions or issues I have forgotten to consider. I will then create a PR with the functionality we agreed upon.
The text was updated successfully, but these errors were encountered: