I'm looking for a way to manage a set of Python data structures which would nicely fit into a relational schema, but without the overhead of having a real database or parsing SQL. The amount of data can be assumed to be small enough to fit conveniently into memory (say, no structure contains more than a million elements).

Most importantly, I would like to have automatically enforced foreign key constraints. Triggering an assertion failure on foreign key constraint violation would be good enough; it's always a programming error.

Here's a real-world example of what I would like to accomplish. I do have the code to do this, but without automatic foreign key constraint checking it's getting error-prone and a mess of asserts.

(These are data structures for code to analyze the execution trace of a machine code program, for the curious. The logger program outputs each unique (previous_instruction_addr, current_instruction_addr, stack_pointer_change) tuple once.)


instrs: set(int) (addresses of machine instructions seen)
next_instrs: dict(int -> set(int)), a dictionary mapping an instruction (which must be in instrs) to a set of instructions (all of which must be in instrs). Relationally, a subset of instrs×instrs.
stk_changes: dict((int,int) -> set(int)), a dictionary mapping a pair of instructions (where the pair of instructions must be in next_instrs) to a set of changes in the stack pointer register
jumps: set((int,int)), a subset of the next_instrs relation deemed to be a jump (including a function call or a return from a function) from one instruction to another. Actually I currently implement this as two dicts, as I need to be able to make queries both of the form "where does this instruction jump?" and "what instructions jump to this instruction?".
calls: set((int,int)), a subset of jumps that are function calls.
rets: set((int,int)), a subset of jumps that are returns from function.
basic_blocks: set(int), a subset of instrs; the first addresses of basic blocks (blocks of code where execution only ever starts in the first instruction, i.e. no jumps to the middle, and only ever ends in the last instructions, i.e. no jumps from the middle)
containing_bb: dict(int -> int), the basic block that contains each instruction. A subset of instrs×basic_blocks
functions: set(int), basic blocks deemed to be starts of a function. A subset of basic_blocks.
function_calls_by_bb: dict(int -> int), functions called by a basic block; a subset of basic_blocks×functions


And so on; you get the idea.

What I'm essentially looking for is a way to manage all this structure and automatically enforce all the foreign key constraints; for example, I would like basic_blocks.add(something) to fail with an error if something is not a member of instrs. Similarly, I would like basic_blocks.remove(something) to fail if something is still referred to by function_calls_by_bb. Clearly, writing assertions in add() and remove() methods for all of these structures is needlessly verbose and error-prone compared to, for example, foreign key constraints in an SQL database schema.

I'm currently playing with sqlalchemy with an in-memory sqlite database, which lets me describe the constraints in a nice way, but I'm ideally looking for something much more lightweight that does not involve database engines. (It may be that a database engine is ultimately the proper way to do what I'm doing, but currently I'm evaluating alternatives.)

Alternatively, if you can think of other ways to manage structures such as this, I'd also be interested in hearing about those.


Question: Is pretending to want to trade before playing a monopoly card objectionable?
Answer: If you are simply trying to determine which resource is the most abundant, can't you just count the remaining resource cards? Isn't that public knowledge?

If so, that would make this tactic unnecessary.