Package: bloop
Bloop is an object mapper for DynamoDB and DynamoDBStreams.Requires Python 3.6+pip install bloopUsageFirst, we need to import all the things:>>>frombloopimport(...BaseModel,Boolean,Column,String,UUID,...GlobalSecondaryIndex,Engine...)Next we’ll define the account model (with streaming enabled), and create the backing table:>>>classAccount(BaseModel):...classMeta:...stream={..."include":{"old","new"}...}...id=Column(UUID,hash_key=True)...name=Column(String)...email=Column(String)...by_email=GlobalSecondaryIndex(projection='keys',hash_key='email')...verified=Column(Boolean,default=False)...>>>engine=Engine()>>>engine.bind(Account)Let’s make a few users and persist them:>>>importuuid>>>admin=Account(id=uuid.uuid4(),email="admin@domain.com")>>>admin.name="Admin McAdminFace">>>support=Account(name="this-is-fine.jpg",email="help@domain.com")>>>support.id=uuid.uuid4()>>>engine.save(admin,support)Or do the same in a transaction:>>>withengine.transaction()astx:...tx.save(admin)...tx.save(support)...>>>And find them again:>>>q=engine.query(...Account.by_email,...key=Account.email=="help@domain.com"...)>>>q.first()Account(email='help@domain.com',id=UUID('d30e343f-f067-4fe5-bc5e-0b00cdeaf2ba'),verified=False)>>>s=engine.scan(...Account,...filter=Account.name.begins_with("Admin")...)>>>s.one()Account(email='admin@domain.com',id=UUID('08da44ac-5ff6-4f70-8a3f-b75cadb4dd79'),name='Admin McAdminFace',verified=False)Let’s find them in the stream:>>>stream=engine.stream(Account,"trim_horizon")>>>next(stream){'key':None,'meta':{'created_at':datetime.datetime(...),'event':{'id':'cbb9a9b45eb0a98889b7da85913a5c65','type':'insert','version':'1.1'},'sequence_number':'100000000000588052489'},'new':Account(email='help@domain.com',id=UUID('d30e343f-...-0b00cdeaf2ba'),name='this-is-fine.jpg',verified=False),'old':None}>>>next(stream){'key':None,'meta':{'created_at':datetime.datetime(...),'event':{'id':'cbdfac5671ea38b99017c4b43a8808ce','type':'insert','version':'1.1'},'sequence_number':'200000000000588052506'},'new':Account(email='admin@domain.com',id=UUID('08da44ac-...-b75cadb4dd79'),name='Admin McAdminFace',verified=False),'old':None}>>>next(stream)>>>next(stream)>>>What’s NextCheck out theUser GuideorPublic API Referenceto create your own nested types, overlapping models,
set up cross-region replication in less than 20 lines, and more!ChangelogThis changelog structure is based onKeep a Changelog v0.3.0.
Bloop followsSemantic Versioning 2.0.0and adraft appendixfor its Public API.Unreleased(no unreleased changes)3.1.0 - 2021-11-11Fixed an issue where copying anIndexwould lose projection information when the projection mode was"include".  This fix should have no effect for most users.  You would only run into this issue if you
were manually callingbind_indexwithcopy=Trueon a projection mode"include"or you subclass
a model that has an index with that projection mode.  This does not require a major version change since
there is no reasonable workaround that would be broken by making this fix.  For example, a user might
decide to monkeypatchIndex.__copy__,bind_indexorrefresh_indexto preserve the projection
information.  Those workarounds will not be broken by this change.  For an example of the issue, seeIssue #147.[Changed]Index.projectionis now asetinstead of alist`.  Since ``Columnimplements__hash__this won’t affect any existing calls that pass in lists.  To remain consistent, this change is reflected
inEngine.search,Search.__init__,Index.__init__, and any docs or examples that refer to passing
lists/sets of Columns.[Fixed]Index.__copy__preservesIndex.projection["included"]when projection mode is"include".3.0.0 - 2019-10-11Remove deprecated keywordatomic=fromEngine.saveandEngine.delete, andType._dumpmust return
abloop.actions.Actioninstance.  See the Migration Guide for context on these changes, and sample code to
easily migrate your existing custom Types.[Added](internal)util.default_contextcan be used to create a new load/dump context and respects existing dict
objects and keys (even if empty).[Changed]Type._dumpmust return abloop.actions.Actionnow.  Most users won’t need to change any code since custom
types usually overridedynamo_dump.  If you have implemented your own_dumpfunction, you can probably
just useactions.wrapandactions.unwrapto migrate:def_dump(self,value,*,context,**kwargs):value=actions.unwrap(value)# the rest of your function herereturnactions.wrap(value)[Removed]The deprecatedatomic=keyword has been removed fromEngine.saveandEngine.delete.The exceptionbloop.exceptions.UnknownTypeis no longer raised and has been removed.(internal)BaseModel._loadandBaseModel._dumphave been removed.  These were not documented or used
anywhere in the code base, andunpack_from_dynamodbshould be used where_loadwas anyway.(internal)Engine._loadandEngine._dumphave been removed.  These were not documented and are trivially
replaced with calls totypedef._loadandtypedef._dumpinstead.(internal)Thedumpedattr for Conditions is no longer needed since there’s no need to dump objects except
at render time.2.4.1 - 2019-10-11Bug fix.  Thanks to @wilfre inPR #141![Fixed]bloop.stream.shard.py::unpack_shardsno longer raises when a Shard in the DescribeStream has a ParentId
that is not also available in the DescribeStream response (the parent shard has been deleted).  Previously the
code would raise while trying to link the two shard objects in memory.  Now, the shard will have a ParentId ofNone.2.4.0 - 2019-06-13Theatomic=keyword forEngine.saveandEngine.deleteis deprecated and will be removed in 3.0.
In 2.4 your code will continue to work but will raiseDeprecationWarningwhen you specify a value foratomic=.TheType._dumpfunction return value is changing toUnion[Any, bloop.Action]in 2.4 to prepare for the
change in 3.0 to exclusively returning abloop.Action.  For built-in types and most custom types that only
overridedynamo_dumpthis is a no-op, but if you callType._dumpyou can usebloop.actions.unwrap()on
the result to get the inner value.  If you have a customType._dumpmethod it must return an action in 3.0.  For
ease of use you can usebloop.actions.wrap()which will specify either theSETorREMOVEaction to match
existing behavior.  Here’s an example of how you can quickly modify your code:# current pre-2.4 method, continues to work until 3.0def_dump(self,value,**kwargs):value=self.dynamo_dump(value,**kwargs)ifvalueisNone:returnNonereturn{self.backing_type:value}# works in 2.4 and 3.0frombloopimportactionsdef_dump(self,value,**kwargs):value=actions.unwrap(value)value=self.dynamo_dump(value,**kwargs)returnactions.wrap(value)Note that this is backwards compatible in 2.4:Type._dumpwill not change unless you opt to pass the newActionobject to it.[Added]SearchIterator.tokenprovides a way to start a new Query or Scan from a previous query/scan’s state.
SeeIssue #132.SearchIterator.move_totakes a token to update the search state.  Count/ScannedCount state are lost when
moving to a token.Engine.deleteandEngine.savetake an optional argumentsync=which can be used to update objects with
the old or new values from DynamoDB after saving or deleting.  See theReturn Valuessection of the User Guide
andIssue #137.bloop.actionsexpose a way to manipulate atomic counters and sets.  See theAtomic Counterssection of the
User Guide andIssue #136.[Changed]Theatomic=keyword forEngine.saveandEngine.deleteemitsDeprecationWarningand will be
removed in 3.0.Type._dumpwill return abloop.action.Actionobject if one is passed in, in preparation for the
change in 3.0.2.3.3 - 2019-01-27Engine.bindis much faster for multi-model tables.  SeeIssue #130.[Changed](internal)SessionWrappercachesDescribeTableresponses.  You can clear these withSessionWrapper.clear_cache; mutating calls such as.enable_ttlwill invalidate the cached description.(internal)EachEngine.bindwill callCreateTableat most once per table.  Subsequent calls tobindwill callCreateTableagain.2.3.2 - 2019-01-27Minor bug fix.[Fixed](internal)bloop.conditions.iter_columnsno longer yieldsNoneonCondition()(or
any other condition whose.columnattribute isNone).2.3.0 - 2019-01-24This release adds support forTransactionsandOn-Demand Billing.  Transactions can include changes across
tables, and provide ACID guarantees at a 2x throughput cost and a limit of 10 items per transaction.
See theUser Guidefor details.withengine.transaction()astx:tx.save(user,tweet)tx.delete(event,task)tx.check(meta,condition=Metadata.worker_id==current_worker)[Added]Engine.transaction(mode="w")returns a transaction object which can be used directly or as a context manager.
By default this creates aWriteTransaction, but you can passmode="r"to create a read transaction.WriteTransactionandReadTransactioncan be prepared for committing with.prepare()which returns aPreparedTransactionwhich can be committed with.commit()some number of times.  These calls are usually
handled automatically when using the read/write transaction as a context manager:# manual calls
tx = engine.transaction()
tx.save(user)
p = tx.prepare()
p.commit()

# equivalent functionality
with engine.transaction() as tx:
    tx.save(user)Meta supportsOn-Demand Billing:class MyModel(BaseModel):
    id = Column(String, hash_key=True)
    class Meta:
        billing = {"mode": "on_demand"}(internal)bloop.session.SessionWrapper.transaction_readandbloop.session.SessionWrapper.transaction_writecan be used to call TransactGetItems and TransactWriteItems
with fully serialized request objects.  The write api requires a client request token to provide idempotency guards,
but does not provide temporal bounds checks for those tokens.[Changed]Engine.loadnow logs atINFOinstead ofWARNINGwhen failing to load some objects.Meta.ttl["enabled"]will now be a literalTrueorFalseafter binding the model, rather than the string
“enabled” or “disabled”.IfMeta.encryptionorMeta.backupsis None or missing, it will now be set after binding the model.Metaand GSI read/write units are not validated if billing mode is"on_demand"since they will be 0 and the
provided setting is ignored.2.2.0 - 2018-08-30[Added]DynamicListandDynamicMaptypes can store arbitrary values, although they will only be loaded as their
primitive, direct mapping to DynamoDB backing types.  For example:class MyModel(BaseModel):
    id = Column(String, hash_key=True)
    blob = Column(DynamicMap)
i = MyModel(id="i")
i.blob = {"foo": "bar", "inner": [True, {1, 2, 3}, b""]}Meta supportsContinuous Backupsfor Point-In-Time Recovery:class MyModel(BaseModel):
    id = Column(String, hash_key=True)
    class Meta:
        backups = {"enabled": True}SearchIteratorexposes anall()method which eagerly loads all results and returns a single list.
Note that the query or scan is reset each time the method is called, discarding any previously buffered state.[Changed]StringandBinarytypes loadNoneas""andb""respectively.Saving an empty String or Binary (""orb"") will no longer throw a botocore exception, and will instead
be treated asNone.  This brings behavior in line with the Set, List, and Map types.2.1.0 - 2018-04-07Added support forServer-Side Encryption.  This uses an AWS-managed Customer Master Key (CMK) stored inKMSwhich ismanaged for free: “You are not charged for the following: AWS-managed CMKs, which are automatically
created on your behalf when you first attempt to encrypt a resource in a supported AWS service.”[Added]Metasupports Server Side Encryption:class MyModel(BaseModel):
    id = Column(String, hash_key=True)
    class Meta:
        encryption = {"enabled": True}2.0.1 - 2018-02-03Fix a bug where the last records in a closed shard in a Stream were dropped.  SeeIssue #87andPR #112.[Fixed]Streamno longer drops the last records from a closed Shard when moving to the child shard.2.0.0 - 2017-11-272.0.0 introduces 4 significant new features:Model inheritance and mixinsTable name templates:table_name_template="prod-{table_name}"TTLsupport:ttl = {"column": "not_after"}Column defaults:verified=Column(Boolean, default=False)
not_after = Column(
    Timestamp,
    default=lambda: (
        datetime.datetime.now() +
        datetime.timedelta(days=30)
    )
)Python 3.6.0 is now the minimum required version, as Bloop takes advantage of__set_name__and__init_subclass__to avoid the need for a Metaclass.A number of internal-only and rarely-used external methods have been removed, as the processes which required them
have been simplified:Column.get, Column.set, Column.deletein favor of their descriptor protocol counterpartsbloop.Type._registeris no longer necessary before using a custom TypeIndex._bindis replaced by helpersbind_indexandrefresh_index.  You should not need to call these.A number of overly-specific exceptions have been removed.[Added]Enginetakes an optional keyword-only arg"table_name_template"which takes either a string used to format
each name, or a function which will be called with the model to get the table name of.  This removes the need to
connect to thebefore_create_tablesignal, which also could not handle multiple table names for the same model.
With this changeBaseModel.Meta.table_namewill no longer be authoritative, and the engine must be consulted to
find a given model’s table name.  An internal functionEngine._compute_table_nameis available, and the
per-engine table names may be added to the model.Meta in the future.  (seeIssue #96)A new exceptionInvalidTemplateis raised when an Engine’s table_name_template is a string but does
not contain the required"{table_name}"formatting key.You can now specify aTTL(seeIssue #87) on a model much like a Stream:class MyModel(BaseModel):
    class Meta:
        ttl = {
            "column": "expire_after"
        }


    id = Column(UUID, hash_key=True)
    expire_after = Column(Timestamp)A new type,Timestampwas added.  This stores adatetime.datetimeas a unix timestamp in whole seconds.CorrespondingTimestamptypes were added to the following extensions, mirroring theDateTimeextension:bloop.ext.arrow.Timestamp,bloop.ext.delorean.Timestamp, andbloop.ext.pendulum.Timestamp.Columntakes an optional kwargdefault, either a single value or a no-arg function that returns a value.
Defaults are applied only duringBaseModel.__init__and not when loading objects from a Query, Scan, or Stream.
If your function returnsbloop.util.missing, no default will be applied.  (seePR #90,PR #105for extensive discussion)(internal)A new abstract interface,bloop.models.IMetawas added to assist with code completion.  This
fully describes the contents of aBaseModel.Metainstance, and can safely be subclassed to provide hints to your
editor:class MyModel(BaseModel):
    class Meta(bloop.models.IMeta):
        table_name = "my-table"
    ...(internal)bloop.session.SessionWrapper.enable_ttlcan be used to enable a TTL on a table.  This SHOULD NOT
be called unless the table was just created by bloop.(internal)helpers for dynamic model inheritance have been added to thebloop.modelspackage:bloop.models.bind_columnbloop.models.bind_indexbloop.models.refresh_indexbloop.models.unbindDirect use is discouraged without a strong understanding of how binding and inheritance work within bloop.[Changed]Python 3.6 is the minimum supported version.BaseModelno longer requires a Metaclass, which allows it to be used as a mixin to an existing class which
may have a Metaclass.BaseModel.Meta.initno longer defaults to the model’s__init__method, and will instead usecls.__new__(cls)to obtain an instance of the model.  You can still specify a custom initialization function:class MyModel(BaseModel):
    class Meta:
        @classmethod
        def init(_):
            instance = MyModel.__new__(MyModel)
            instance.created_from_init = True
    id = Column(...)ColumnandIndexsupport the shallow copy method__copy__to simplify inheritance with custom subclasses.
You may override this to change how your subclasses are inherited.DateTimeexplicitly guards againsttzinfo is None, sincedatetime.astimezonestarted silently allowing
this in Python 3.6 – you should not use a naive datetime for any reason.Column.model_nameis nowColumn.name, andIndex.model_nameis nowIndex.name.Column(name=)is nowColumn(dynamo_name=)andIndex(name=)is nowIndex(dynamo_name=)The exceptionInvalidModelis raised instead ofInvalidIndex.The exceptionInvalidSearchis raised instead of the following:InvalidSearchMode,InvalidKeyCondition,InvalidFilterCondition, andInvalidProjection.(internal)bloop.session.SessionWrappermethods now require an explicit table name, which is not read from the
model name.  This exists to support different computed table names per engine.  The following methods now require
a table name:create_table,describe_table(new),validate_table, andenable_ttl(new).[Removed]bloop no longer supports Python versions below 3.6.0bloop no longer depends ondeclareColumn.get,Column.set, andColumn.deletehelpers have been removed in favor of using the Descriptor
protocol methods directly:Column.__get__,Column.__set__, andColumn.__delete__.bloop.Typeno longer exposes a_registermethod; there is no need to register types before using them,
and you can remove the call entirely.Column.model_name,Index.model_name, and the kwargsColumn(name=),Index(name=)(see above)The exceptionInvalidIndexhas been removed.The exceptionInvalidComparisonOperatorwas unused and has been removed.The exceptionUnboundModelis no longer raised duringEngine.bindand has been removed.The exceptionsInvalidSearchMode,InvalidKeyCondition,InvalidFilterCondition, andInvalidProjectionhave been removed.(internal)Index._bindhas been replaced with the more complete solutions inbloop.models.bind_columnandbloop.models.bind_index.1.3.0 - 2017-10-08This release is exclusively to prepare users for thename/model_name/dynamo_namechanges coming in 2.0;
your 1.2.0 code will continue to work as usual but will raiseDeprecationWarningwhen accessingmodel_nameon
a Column or Index, or when specifying thename=kwarg in the__init__method ofColumn,GlobalSecondaryIndex, orLocalSecondaryIndex.Previously it was unclear ifColumn.model_namewas the name of this column in its model, or the name of the model
it is attached to (eg. a shortcut forColumn.model.__name__).  Additionally thename=kwarg actually mapped to
the object’s.dynamo_namevalue, which was not obvious.Now theColumn.nameattribute will hold the name of the column in its model, whileColumn.dynamo_namewill
hold the name used in DynamoDB, and is passed during initialization asdynamo_name=.  Accessingmodel_nameor
passingname=during__init__will raise deprecation warnings, and bloop 2.0.0 will remove the deprecated
properties and ignore the deprecated kwargs.[Added]Column.nameis the new home of theColumn.model_nameattribute.  The same is true forIndex,GlobalSecondaryIndex, andLocalSecondaryIndex.The__init__method ofColumn,Index,GlobalSecondaryIndex, andLocalSecondaryIndexnow takesdynamo_name=in place ofname=.[Changed]AccessingColumn.model_nameraisesDeprecationWarning, and the same for Index/GSI/LSI.ProvidingColumn(name=)raisesDeprecationWarning, and the same for Index/GSI/LSI.1.2.0 - 2017-09-11[Changed]When a Model’s Meta does not explicitly setread_unitsandwrite_units, it will only default to 1/1 if the
table does not exist and needs to be created.  If the table already exists, any throughput will be considered
valid.  This will still ensure new tables have 1/1 iops as a default, but won’t fail if an existing table has more
than one of either.There is no behavior change for explicitintegervalues ofread_unitsandwrite_units: if the table does
not exist it will be created with those values, and if it does exist then validation will fail if the actual values
differ from the modeled values.An explicitNonefor eitherread_unitsorwrite_unitsis equivalent to omitting the value, but allows
for a more explicit declaration in the model.Because this is a relaxing of a default only within the context of validation (creation has the same semantics) the
only users that should be impacted are those that do not declareread_unitsandwrite_unitsand rely on the
built-in validationfailingto match on values != 1.  Users that rely on the validation to succeed on tables with
values of 1 will see no change in behavior.  This fits within the extended criteria of a minor release since there
is a viable and obvious workaround for the current behavior (declare 1/1 and ensure failure on other values).When a Query or Scan has projection type “count”, accessing thecountorscannedproperties will
immediately execute and exhaust the iterator to provide the count or scanned count.  This simplifies the previous
workaround of callingnext(query, None)before usingquery.count.[Fixed]Fixed a bug where a Query or Scan with projection “count” would always raise KeyError (seeIssue #95)Fixed a bug where resetting a Query or Scan would cause__next__to raisebotocore.exceptions.ParamValidationError(seeIssue #95)1.1.0 - 2017-04-26[Added]Engine.bindtakes optional kwargskip_table_setupto skip CreateTable and DescribeTable calls (seeIssue #83)Index validates against a superset of the projection (seeIssue #71)1.0.3 - 2017-03-05Bug fix.[Fixed]Stream orders records on the integer of SequenceNumber, not the lexicographical sorting of its string
representation.  This is an annoying bug, becauseas documentedweshouldbe using lexicographical sorting
on the opaque string.  However, without leading 0s that sort fails, and we must assume the string represents an
integer to sort on.  Particularly annoying, tomorrow the SequenceNumber could start with non-numeric characters
and still conform to the spec, but the sorting-as-int assumption breaks.  However, we can’t properly sort without
making that assumption.1.0.2 - 2017-03-05Minor bug fix.[Fixed]extension types inext.arrow,ext.delorean, andext.pendulumnow load and dumpNonecorrectly.1.0.1 - 2017-03-04Bug fixes.[Changed]Thearrow,delorean, andpendulumextensions now have a default timezone of"utc"instead ofdatetime.timezone.utc.  There are open issues for both projects to verify if that is the expected behavior.[Fixed]DynamoDBStreams return a Timestamp for each record’s ApproximateCreationDateTime, which botocore is translating
into a real datetime.datetime object.  Previously, the record parser assumed an int was used.  While this fix is
a breaking change for an internal API, this bug broke the Stream iterator interface entirely, which means no one
could have been using it anyway.1.0.0 - 2016-11-161.0.0 is the culmination of just under a year of redesigns, bug fixes, and new features.  Over 550 commits, more than
60 issues closed, over 1200 new unit tests.  At an extremely high level:The query and scan interfaces have been polished and simplified.  Extraneous methods and configuration settings have
been cut out, while ambiguous properties and methods have been merged into a single call.A new, simple API exposes DynamoDBStreams with just a few methods; no need to manage individual shards, maintain
shard hierarchies and open/closed polling.  I believe this is a first since the Kinesis Adapter and KCL, although
they serve different purposes.  When a single worker can keep up with a model’s stream, Bloop’s interface is
immensely easier to use.Engine’s methods are more consistent with each other and across the code base, and all of the configuration settings
have been made redundant.  This removes the need forEngineViewand its associated temporary config changes.Blinker-powered signals make it easy to plug in additional logic when certain events occur: before a table is
created; after a model is validated; whenever an object is modified.Types have been pared down while their flexibility has increased significantly.  It’s possible to create a type that
loads another object as a column’s value, using the engine and context passed into the load and dump functions.  Be
careful with this; transactions on top of DynamoDB are very hard to get right.See the Migration Guide above for specific examples of breaking changes and how to fix them, or theUser Guidefor a tour of the new Bloop.  Lastly, the Public and Internal API References are
finally available and should cover everything you need to extend or replace whole subsystems in Bloop
(if not, please open an issue).[Added]bloop.signalsexposes Blinker signals which can be used to monitor object changes, when
instances are loaded from a query, before models are bound, etc.before_create_tableobject_loadedobject_savedobject_deletedobject_modifiedmodel_boundmodel_createdmodel_validatedEngine.streamcan be used to iterate over all records in a stream, with a total ordering over approximate
record creation time.  Useengine.stream(model, "trim_horizon")to get started.  See theUser Guidefor details.New exceptionsRecordsExpiredandShardIteratorExpiredfor errors in stream stateNew exceptionsInvalid*for bad input subclassBloopExceptionandValueErrorDateTimetypes for the three most common date time libraries:bloop.ext.arrow.DateTimebloop.ext.delorean.DateTimebloop.ext.pendulum.DateTimemodel.Metahas a new optional attributestreamwhich can be used to enable a stream on the model’s table.model.Metaexposes the sameprojectionattribute asIndexso that(index ormodel.Meta).projectioncan be used interchangeablyNewStreamclass exposes DynamoDBStreams API as a single iterable with powerful seek/jump options, and simple
json-friendly tokens for pausing and resuming iteration.Over 1200 unit tests addedInitial integration tests added(internal)bloop.conditions.ReferenceTrackerhandles building#n0,:v1, and associated values.
Useany_refto build a reference to a name/path/value, andpop_refswhen backtracking (eg. when a value is
actually another column, or when correcting a partially valid condition)(internal)bloop.conditions.renderis the preferred entry point for rendering, and handles all permutations
of conditions, filters, projections.  Use overConditionRendererunless you need very specific control over
rendering sequencing.(internal)bloop.session.SessionWrapperexposes DynamoDBStreams operations in addition to previousbloop.Clientwrappers around DynamoDB client(internal)New supporting classesstreams.buffer.RecordBuffer,streams.shard.Shard, andstreams.coordinator.Coordinatorto encapsulate the hell^Wjoy that is working with DynamoDBStreams(internal)New classutil.Sentinelfor placeholder values likemissingandlast_tokenthat provide clearer docstrings, instead of showingfunc(...,default=object<0x...>)these will showfunc(...,default=Sentinel<[Missing]>)[Changed]bloop.Columnemitsobject_modifiedon__set__and__del__Conditions now check if they can be used with a column’stypedefand raiseInvalidConditionwhen they can’t.
For example,containscan’t be used onNumber, nor>onSet(String)bloop.Engineno longer takes an optionalbloop.Clientbut instead optionaldynamodbanddynamodbstreamsclients (usually created fromboto3.client("dynamodb")etc.)Engineno longer takes**config– its settings have been dispersed to their local touch pointsatomicis a parameter ofsaveanddeleteand defaults toFalseconsistentis a parameter ofload,query,scanand defaults toFalseprefetchhas no equivalent, and is baked into the new Query/Scan iterator logicstrictis a parameter of aLocalSecondaryIndex, defaults toTrueEngineno longer has acontextto create temporary views with different configurationEngine.bindis no longer by keyword arg only:engine.bind(MyBase)is acceptable in addition toengine.bind(base=MyBase)Engine.bindemits new signalsbefore_create_table,model_validated, andmodel_boundEngine.deleteandEngine.savetake*objsinstead ofobjsto easily save/delete small multiples of
objects (engine.save(user, tweet)instead ofengine.save([user,tweet]))Engineguards against loading, saving, querying, etc against abstract modelsEngine.loadraisesMissingObjectsinstead ofNotModified(exception rename)Engine.scanandEngine.querytake all query and scan arguments immediately, instead of using the builder
pattern.  For example,engine.scan(model).filter(Model.x==3)has becomeengine.scan(model,filter=Model.x==3).bloop.exceptions.NotModifiedrenamed tobloop.exceptions.MissingObjectsAny code that raisedAbstractModelExceptionnow raisesUnboundModelbloop.types.DateTimeis now backed bydatetime.datetimeinstead ofarrow.  Only supports UTC now, no
local timezone.  Use thebloop.ext.arrow.DateTimeclass to continue usingarrow.The query and scan interfaces have been entirely refactored:count,consistent,ascendingand other
properties are part of theEngine.query(...)parameters.all()is no longer needed, asEngine.scanand.queryimmediately return an iterable object.  There is noprefetchsetting, orlimit.Thecompleteproperty for Query and Scan have been replaced withexhausted, to be consistent with the Stream
moduleThe query and scan iterator no longer cache resultsTheprojectionparameter is now required forGlobalSecondaryIndexandLocalSecondaryIndexCallingIndex.__set__orIndex.__del__will raiseAttributeError.  For example,some_user.by_email = 3raises ifUser.by_emailis a GSIbloop.Numberreplacesbloop.Floatand takes an optionaldecimal.Contextfor converting numbers.
For a less strict,lossyFloattype see thePatternssection of the User Guidebloop.String.dynamo_dumpno longer callsstr()on the value, which was hiding bugs where a non-string
object was passed (eg.some_user.name = object()would save with a name of<object<0x...>)bloop.DateTimeis now backed bydatetime.datetimeand only knows UTC in a fixed format.  Adapters forarrow,delorean, andpendulumare available inbloop.extbloop.DateTimedoes not support naive datetimes; they must always have atzinfodocs:use RTD themerewritten three timesnow includes public and internal api references(internal)Path lookups onColumn(eg.User.profile["name"]["last"]) use simpler proxies(internal)Proxy behavior split out fromColumn’s base classbloop.conditions.ComparisonMixinfor a cleaner namespace(internal)bloop.conditions.ConditionRendererrewritten, uses a newbloop.conditions.ReferenceTrackerwith a much clearer api(internal)ConditionRenderercan backtrack references and handles columns as values (eg.User.name.in_([User.email,"literal"]))(internal)_MultiConditionlogic rolled intobloop.conditions.BaseCondition,AndConditionandOrConditionno longer have intermediate base class(internal)AttributeExistslogic rolled intobloop.conditions.ComparisonCondition(internal)bloop.trackingrolled intobloop.conditionsand is hooked into theobject_*signals.
Methods are no longer called directly (eg. no need fortracking.sync(some_obj, engine))(internal)update condition is built from a set of columns, not a dict of updates to apply(internal)bloop.conditions.BaseConditionis a more comprehensive base class, and handles all manner of
out-of-order merges (and(x, y)vsand(y, x)where x is anandcondition and y is not)(internal)almost all*Conditionclasses simply implement__repr__andrender;BaseConditiontakes care of everything else(internal)bloop.Clientbecamebloop.session.SessionWrapper(internal)Engine._dumptakes an optionalcontext,**kwargs, matching the
signature ofEngine._load(internal)BaseModelno longer implements__hash__,__eq__, or__ne__butModelMetaclasswill
always ensure a__hash__function when the subclass is created(internal)FilterandFilterIteratorrewritten entirely in thebloop.searchmodule across multiple
classes[Removed]AbstractModelExceptionhas been rolled intoUnboundModelTheall()method has been removed from the query and scan iterator interface.  Simply iterate withnext(query)orfor result in query:Query.resultsandScan.resultshave been removed and results are no longer cached.  You can begin the
search again withquery.reset()Thenew_base()function has been removed in favor of subclassingBaseModeldirectlybloop.Floathas been replaced bybloop.Number(internal)bloop.engine.LoadManagerlogic was rolled intobloop.engine.load(...)EngineViewhas been removed since engines no longer have a baselineconfigand don’t need a
context to temporarily modify it(internal)Engine._updatehas been removed in favor ofutil.unpack_from_dynamodb(internal)Engine._instancehas been removed in favor of directly creating instances frommodel.Meta.init()inunpack_from_dynamodb[Fixed]Column.contains(value)now rendersvaluewith the column typedef’s inner type.  Previously, the container
type was used, soData.some_list.contains("foo"))would render as(contains(some_list, ["f", "o","o"]))instead of(contains(some_list,"foo"))Setrenders correct wire format – previously, it incorrectly sent{"SS":[{"S":"h"},{"S":"i"}]}instead
of the correct{"SS": ["h","i"]}(internal)SetandListexpose aninner_typedeffor conditions to force rendering of inner values
(currently only used byContainsCondition)0.9.13 - 2016-10-31[Fixed]Setwas rendering an invalid wire format, and now renders the correct “SS”, “NS”, or “BS” values.SetandListwere renderingcontainsconditions incorrectly, by trying to dump each value in the
value passed to contains.  For example,MyModel.strings.contains("foo")would rendercontains(#n0,:v1)where:v1was{"SS":[{"S":"f"},{"S":"o"},{"S":"o"}]}.  Now, non-iterable values are rendered
singularly, so:v1would be{"S": "foo"}.  This is a temporary fix, and only works for simple cases.
For example,List(List(String))will still break when performing acontainscheck.This is fixed correctly in 1.0.0and you should migrate as soon as possible.0.9.12 - 2016-06-13[Added]model.Metanow exposesgsisandlsis, in addition to the existingindexes.  This simplifies code that
needs to iterate over each type of index and not all indexes.[Removed]engine_for_profilewas no longer necessary, since the client instances could simply be created with a given
profile.0.9.11 - 2016-06-12[Changed]bloop.Clientnow takesboto_client, which should be an instance ofboto3.client("dynamodb")instead of
aboto3.session.Session.  This lets you specify endpoints and other configuration only exposed during the
client creation process.Engineno longer uses"session"from the config, and instead takes aclientparam which should be an
instance ofbloop.Client.bloop.Client will be going away in 1.0.0and Engine will simply take the boto3
clients directly.0.9.10 - 2016-06-07[Added]New exceptionAbstractModelExceptionis raised when attempting to perform an operation which requires a
table, on an abstract model.  Raised by all Engine functions as well asbloop.Clientoperations.[Changed]Engineoperations raiseAbstractModelExceptionwhen attempting to perform operations on abstract models.Previously, models were considered non-abstract ifmodel.Meta.abstractwas False, or there was no value.
Now,ModelMetaclasswill explicitly setabstractto False so thatmodel.Meta.abstractcan be used
everywhere, instead ofgetattr(model.Meta, "abstract", False).0.9.9 - 2016-06-06[Added]Columnhas a new attributemodel, the model it is bound to.  This is set during the model’s creation by
theModelMetaclass.[Changed]Engine.bindwill now skip intermediate models that are abstract.  This makes it easier to pass abstract models,
or models whose subclasses may be abstract (and have non-abstract grandchildren).0.9.8 - 2016-06-05(no public changes)0.9.7 - 2016-06-05[Changed]Conditions implement__eq__for checking if two conditions will evaluate the same.  For example:>>> large = Blob.size > 1024**2
>>> small = Blob.size < 1024**2
>>> large == small
False
>>> also_large = Blob.size > 1024**2
>>> large == also_large
True
>>> large is also_large
False0.9.6 - 2016-06-040.9.6 is the first significant change to how Bloop binds models, engines, and tables.  There are a few breaking
changes, although they should be easy to update.Where you previously created a model from the Engine’s model:frombloopimportEngineengine=Engine()classMyModel(engine.model):...You’ll now create a base without any relation to an engine, and then bind it to any engines you want:frombloopimportEngine,new_baseBaseModel=new_base()classMyModel(BaseModel):...engine=Engine()engine.bind(base=MyModel)# or base=BaseModel[Added]A new functionengine_for_profiletakes a profile name for the config file and creates an appropriate session.
This is a temporary utility, sinceEnginewill eventually take instances of dynamodb and dynamodbstreams
clients.This will be going away in 1.0.0.A new base exceptionBloopExceptionwhich can be used to catch anything thrown by Bloop.A new functionnew_base()creates an abstract base for models.  This replacesEngine.modelnow that multiple
engines can bind the same model.This will be going away in 1.0.0which will provide aBaseModelclass.[Changed]Thesessionparameter toEngineis now part of theconfigkwargs.  The underlyingbloop.Clientis
no longer created inEngine.__init__, which provides an opportunity to swap out the client entirely before
the firstEngine.bindcall.  The semantics of session and client are unchanged.Engine._load,Engine._dump, and all Type signatures now pass an engine explicitly through thecontextparameter.  This was mentioned in 0.9.2 andcontextis now required.Engine.bindnow binds the given classand all subclasses.  This simplifies most workflows, since you can
now create a base withMyBase = new_base()and then bind every model you create withengine.bind(base=MyBase).All exceptions now subclass a new base exceptionBloopExceptioninstead ofException.Vector typesSet,List,Map, andTypedMapaccept a typedef ofNoneso they can raise a more
helpful error message.This will be reverted in 1.0.0and will once again be a required parameter.[Removed]Engine no longer hasmodel,unbound_models, ormodelsattributes.Engine.modelhas been replaced
by thenew_base()function, and models are bound directly to the underlying type engine without tracking
on theEngineinstance itself.EngineView dropped the corresponding attributes above.0.9.5 - 2016-06-01[Changed]EngineViewattributes are now properties, and point to the underlying engine’s attributes; this includesclient,model,type_engine, andunbound_models.  This fixed an issue when usingwithengine.context(...)as view:to perform operations on models bound to the engine but not the engine view.EngineView will be going away in 1.0.0.0.9.4 - 2015-12-31[Added]Engine functions now take optional config parameters to override the engine’s config.  You should update your code to
use these values instead ofengine.config, sinceengine.config is going away in 1.0.0.Engine.deleteandEngine.saveexpose theatomicparameter, whileEngine.loadexposesconsistent.Added theTypedMapclass, which provides dict mapping for a single typedef over any number of keys.
This differs fromMap, which must know all keys ahead of time and can use different types.TypedMaponly
supports a single type, but can have arbitrary keys.This will be going away in 1.0.0.0.9.2 - 2015-12-11[Changed]Type functions_load,_dump,dynamo_load,dynamo_dumpnow take an optional keyword-only argcontext.  This dict will become required in 0.9.6, and contains the engine
instance that should be used for recursive types.  If your type currently usescls.Meta.bloop_engine,
you should start usingcontext["engine"]in the next release.  Thebloop_engineattribute is being removed,
since models will be able to bind to multiple engines.0.9.1 - 2015-12-07(no public changes)0.9.0 - 2015-12-07