¿Cómo modelar una restricción ‘ÚNICA’ en SQLAlchemy?

Estoy escribiendo una aplicación Flask / SQLAlchemy en la que tengo usuarios y grupos.

Los usuarios pueden pertenecer a varios grupos y tienen un número único dentro de cada grupo . Al preguntar sobre cómo modelar la base de datos, se me recomendó usar la siguiente estructura de tabla para mi relación de muchos a muchos:

TABLE UserGroups GroupID UserID UserNumber PRIMARY KEY (GroupID, UserID) UNIQUE (GroupID, UserNumber) FOREIGN KEY (GroupID) REFERENCES Groups (GroupID) FOREIGN KEY (UserID) REFERENCES Users (UserID) 

Ahora sé cómo crear una relación regular de muchos a muchos con SQLAlchemy, pero no sé cómo representar la restricción UNIQUE con el campo UserNumber adicional.

No tengo mucha experiencia con el diseño de bases de datos, ORM y SQLAlchemy, por lo que esto puede ser obvio, pero no puedo encontrar una manera de expresslo.

Una de las cosas que no entiendo es: al usar una relación regular de muchos a muchos, mi clase de User tiene groups atributos similares a una lista que contienen todos los grupos a los que pertenece, pero esto oculta completamente la UserGroups unión de UserGroups y No sé cómo acceder al campo UserNumber .

Todo esto es un poco borroso para mí. ¿Tiene algún buen ejemplo o explicación sobre cómo hacer algo así con SQLAlchemy?

Cleg ya responde la primera parte de la pregunta (sobre la creación de una restricción única con varias columnas).

Sin embargo, el enfoque predeterminado de muchos a muchos no funciona si desea tener columnas adicionales en la tabla de asignación. En su lugar, debe utilizar el patrón de objeto de asociación . Además, puede simplificar el acceso entre el usuario y el grupo con un association_proxy .

El proxied_association.py de los ejemplos de SQLAlchemy debería ser un buen lugar para comenzar.

Utilice UniqueConstraint en su modelo. En detalle se describe en esta pregunta: sqlalchemy único en varias columnas

En cuanto a las relaciones de muchos a muchos, SQLAlchemy tiene un tutorial bastante bueno .

PD. Lo siento, me he perdido la segunda parte de la respuesta (es más complejo de lo que pensaba, así que vea la respuesta de @schlamar), pero la primera parte sigue siendo correcta.