Package: mimics
IntroductionMimic is a small tool with the intention to defer actions done on objects or classes. These actions can then be executed at a later date. It's main goal is to solve the chicken-and-egg design conundrum.When you find yourself in a chicken-and-egg situation within your own code, it's most likely attributed to a sub-optimal project design.
If this is the case, it's probably advisable to rethink your project structure.Sometimes, though, when working with 3rd party libraries, you just don't have the choice, and the design of one library does not mesh with that of another.
Out of spite (I'm looking at you <insert most libraries that require an initialized instance to define global scoped decorators>), I started writing this library so that I had control over "when" I initialized "what", while being able to do it in a controlled local scope without losing the ability to use global definitions.BIG FAT DISCLAIMER: I wouldn't use this lib in production code, not in its current state at least :) It needs some more battle testing before I can comfortably say it's stable. Feel free to contribute to this battle testing.QuickstartThe core of this library is theDeferredobject, that basically behaves like a mock object. The only difference is that theDeferredobject does not have reserved names, so you can do literallyanyoperation on it.Of course, that means that you, the user, won't be able to use this deferred object as the driver. For this we'll need a handler object that set things in motion, and ties things together when needed.frommimicsimportMimic# Make the handler objectmimic=Mimic()# Make an object, using the factory on the handler object, that will record all actionshusk=mimic.husk()# Do the deferred operations you want to doresult=husk+3# Replay anything done on the deferred object onto another objectmimic.absorb(husk).as_being(5)# Doing an additional `is True` to ensure to result is a boolean and not a deferred object# (because, yes, even these actions are deferred before playing)assert(result==8)isTrueA more elaborate exampleThis example won't make much sense, as Flask-SQLAlchemy plays quite nicely when it comes to having control over the local scope while still performing global actions, but I thought it was a nice example of what the library is capable of. Here we'll defer the creation, initialization and persisting of an SQLAlchemy model.Once we've done all we wanted, we can play it whenever it suits us best.# Make the handler and deferred objectmimic=Mimic()husk=mimic.husk()# Defer the making of an SQLA model using the deferred objectclassMyModel(husk.Model):id=husk.Column(husk.Integer,primary_key=True)name=husk.Column(husk.String(255),nullable=False,unique=True)# Defer the db creationhusk.create_all()# Defer the initialization and persisting of an instancemy_model=MyModel(name="test")husk.session.add(my_model)husk.session.commit()# Make the actual SQLA db objectapp=Flask(__name__)app.config['SQLALCHEMY_DATABASE_URI']="sqlite:///:memory:"app.config['SQLALCHEMY_TRACK_MODIFICATIONS']=Falsedb=SQLAlchemy(app)# Replay deferred actions as being the dbmimic.absorb(husk).as_being(db)# Verify it workedmodels=MyModel.query.all()assertlen(models)==1assertmodels[0].name=="test"How it worksAs mentioned above, the entire library revolves around theDeferredobject. Due to being able to do, well, almost anything with this object, it's important that you don't initialize a deferred object yourself (unless you know what you're doing). It's important that you make an instance of this class through the factory method that is available on aMimicinstance.Basically theDeferredobject can be in two states:suspendedunsuspendedA shocker, I know. Whenever theDeferredobject is suspended, it will record any action done upon it. Whenever an attribute is accessed, a method is called, or anything that returns a value, it will create and return a new instance of theDeferredobject, that in turn also records actions done with it.Once you're ready to play your deferred actions, bringing it to an unsuspended state, the recorded actions will be re-played on the chosen object. From then on, the spawned deferred objects will work as a proxy that forwards any request to the subject it's related to.PitfallsProxy objectBecause this library is not doing black magic (or at least, not an aweful lot 😉), it's important to know that any subject that theDeferredobject shadows, will nevertrulybe itself after unsuspending. We're not manipulating the virtual memory, manipulating local and global variables or patching imported modules (mind you, I've thought about it).While it may look like you're interacting with the subject itself, you'll always be interacting with a proxy object that looks and feels like its subject. As such, some kinks might pop up in certain cases (cfr. BIG FAT DISCLAIMER).The Deferred object's limitsIt's important to note that only operations performedondeferred objects are allowed. Performing operationswithdeferred objects will go horribly wrong.For instancehusk=Mimic().husk()result=5+huskwill result in a TypeError