Ok I've got it - Is this the best way to do it? - I created a generic id field table as such:
class PaymentDetails(Base):
__tablename__ = 'payment_details'
id = Column(Integer, primary_key=True)
type = Column(PaymentType.db_type(), nullable=False)
where PaymentType uses the declarative enum recipe and and then subclassed this for the various payment methods:
@concrete
@do_unique_index('paypal_unique_details', 'env', 'main_email', 'sub_email')
class Paypal(Base):
__tablename__ = 'paypal_details'
id = Column(ForeignKey('payment_details.id'), primary_key=True)
# The rest of the implementation
#
@concrete
@do_unique_index('credit_card_unique_details', 'env', 'card_number')
class CreditCard(Base):
__tablename__ = 'card_details'
id = Column(ForeignKey('payment_details.id'), primary_key=True)
# The rest of the implementation
#
@concrete
@do_unique_index('time_code_unique_details', 'env', 'code')
class TimeCodes(Base):
__tablename__ = 'code_details'
id = Column(ForeignKey('payment_details.id'), primary_key=True)
# The rest of the implementation
#
(Where concrete and do_unique_index set the relevant __mapper_args__ and __table_args__). I then set the description field of the PaymentType enum values to be each of these classes, so that to look up a payment I can query for a PaymentDetails object, then get an id and a type from that, say id and Paypal, to perform a second query for the Paypal with that id.
My code for adding sets of details is fairly simple in that in a single transaction, it adds the next logical id to the PaymentDetails table with the type of the payment details we are trying to create, and then adds an entry to that table with the details I want to enter. I can then add methods to these ORM classes to handle the different ways that we would handle buying, selling, and refunding for each method, such that they can be treated as identical.
You then need to switch on FK constraints as van mentioned - I did so by adding the FK listener to the helper class I use for DB access.