ObjectBox Python 5.0 is a massive update with many highlights:
- Performance: enable codegen to boost CRUD operations to speeds that beat any Python ORM
- Improved packaging: platform-specific wheels
- More powerful Query and Box operations with an easy-to-use API
- ObjectBox Sync to synchronize your data with a server and/or MongoDB
Upgrading from 4.x with pip: uninstall objectbox first (pip uninstall objectbox, then pip install objectbox).
An in-place pip install --upgrade objectbox would fail (e.g. "cannot import name 'Store' from 'objectbox' (unknown
location)"); repair via pip install --force-reinstall --no-deps objectbox-api. Fresh installs and uv are not affected.
- Faster reads (get, queries): copying an object's data from the database takes ~0.4 µs instead of ~1 µs.
Transactions (
read_tx(),write_tx(), also used internally) have ~0.7 µs less overhead. Together, e.g.box.get()of a small object takes ~4.3 µs instead of ~6.9 µs (with the FlatBuffers codegen). - Faster put of a single new object: its ID is assigned while putting, saving a call to reserve it (~0.3 µs)
- Faster import:
import objectboxno longer imports numpy (also not via flatbuffers, which imports numpy) and takes about half the time. numpy is imported when needed, e.g. for vector properties or the first put or get. Importing numpy may start BLAS threads (OpenBLAS: one per CPU core) that busy-wait for ~0.1 s, slowing down short-lived processes. With the experimental FlatBuffers codegen (see below), entities without vector or flex properties never import numpy or flatbuffers. - Faster opening of many stores, e.g. one store per user or agent. A model is synced with the model JSON file
only once per process (also when given by name, e.g. the default model), saving about 0.1 ms per open.
The new option
skip_model_syncadditionally opens existing stores with the schema stored in their database instead of checking it against the model, which halves the time to open (see its documentation for when to use it). - Experimental: FlatBuffers codegen for putting objects, enabled via
objectbox.experimental.enable_flatbuffers_codegen()(off by default; replaces the fast builder if that is enabled). Like the fast builder, but generates Python code per entity at its first put, which is again ~2x faster for small objects (~25x faster than the generic builder); putting 10,000 objects of the test entity ~5x faster than the generic builder. Codegen also reads objects (get, queries) with generated code, returning the same objects: ~3.5x faster per object for a small entity, ~8x faster for 21 properties. The code is compiled per process at the first put and first read of an entity (~2.8 ms for both with 38 properties); the new optioncache_dircaches the compiled code for other processes (~0.5 ms then), e.g. for short-lived ones. - Experimental: a fast FlatBuffers builder for putting objects, enabled via
objectbox.experimental.enable_fast_flatbuffers_builder()(off by default). It produces the same bytes as before, with the layout precomputed per entity instead of using the genericflatbuffers.Builder; marshalling an object with a string and two numbers gets ~12x faster, putting 10,000 objects of the test entity ~4x faster.
- Packaging: the Python code is now in the new package
objectbox-apiand the ObjectBox C library in the new packageobjectbox-clib;objectboxinstalls both (it contains no code itself). Installobjectboxas before, but when upgrading from 4.x with pip, uninstall it first (see above). - C library package
objectbox-clib: there is a wheel for each platform, so only the library for your platform is downloaded (was: libraries for all platforms): Linux x86_64, aarch64 and armv7l (glibc 2.28+), macOS 11+ (universal) and Windows x64 and ARM64 (new).
Breaking: Linux ARMv6hf (e.g. Raspberry Pi Zero/1) is no longer supported, as PyPI does not support wheels for it. - New platform: Windows ARM64 (64-bit ARM, e.g. Snapdragon-based devices)
- Linux: support 32-bit Python on a 64-bit kernel, e.g. Raspberry Pi OS 32-bit on a Raspberry Pi 4/5 (64-bit kernel by default); this failed to load the library before.
- Requires Python 3.7 or newer (the package metadata claimed 3.4 before, but the code required 3.7 already)
- Update to ObjectBox C library 5.3.2 (was 4.0.0), see https://github.com/objectbox/objectbox-c/blob/main/CHANGELOG.md
- Dependency flatbuffers: any version from 24.3.25 on is accepted now (was pinned to 24.3.25); tested up to 25.12.19
- Order results via
QueryBuilder.order(property, flags), e.g.box.query().order(Person.name).build();OrderFlags(exported by the top-level module) adjust the order, e.g.DESCENDINGorCASE_SENSITIVE;order_desc(property)orders descending - Link queries over to-one relations (
Relationproperties, see below), like in Java and Dart:QueryBuilder.link(relation, condition)matches objects whose related object matches the condition, e.g. orders of customers named "Ann" viaqb = box.query(); qb.link(Order.customer_id, Customer.name.equals("Ann"));backlink(relation, condition)uses a relation in reverse, e.g. customers with a matching order. Both return the builder of the linked entity to link further (any number of relations);set_parameter()also changes conditions on linked entities. - Conditions on
DateandDateNanoproperties take adatetimeorfloatseconds, like putting objects, e.g.Task.due.less_than(datetime.now(timezone.utc)); ints are still milliseconds (Date) or nanoseconds (DateNano). This failed with anException("Unsupported type") before. find_with_scores()andfind_ids_with_scores()return named tuples,ObjectWithScore(object,score) andIdWithScore(id,score), named like in Java and Dart; they are still tuples, e.g. forfor obj, score in ...- New
Query.find_first()(the first object, by ID or the query's order; None if none) andQuery.find_unique()(the only object, None if none; raisesNonUniqueResultErrorif there are more), like in Java and Dart HnswIndex(vector_cache_hint_size_kb=...)takes an int (KB), like in Java and Dart (it was annotated as a float, which failed with actypes.ArgumentErrorwhen the store was opened); other types raise aTypeErrorright away- Vector search: the condition is now
nearest_neighbors(query_vector, max_result_count), named like in the other ObjectBox languages (Java:nearestNeighbors, Dart:nearestNeighborsF32).nearest_neighbor()still works but is deprecated (emits aDeprecationWarning). - New "one of" conditions
String.one_of(values)and, for integer properties,one_of(values)andnot_one_of(values); their values can be changed on a built query viaset_parameter()(see below) - New conditions
is_null()andis_not_null()(e.g.Task.text.is_null()), also asQueryBuilder.is_null()andnot_null(). Null values (put withNone) do not match conditions on the default value, e.g.equals(""). QueryBuilder.alias()remembers the property of the last condition, soset_parameter_alias()sets values by the property's type, as for aliases of conditions (PropertyQueryCondition.alias()); e.g. adatetimefor aDate(was rejected: the value alone decided the type)set_parameter(property, value)andset_parameter_alias(alias, value)change the value of a condition; the property type decides how, e.g. a list sets the vector of a nearest neighbor search or the values of a "one of" condition, andDateproperties also take adatetime.set_parameters(property, a, b)andset_parameters_alias(alias, a, b)change the two values ofbetween(). New are also parameters for floating point and bytes properties, anddescribe_parameters();describe()describes the query itself (like in Java and Dart).
Breaking: the typed methodsset_parameter_string(),set_parameter_int()andset_parameter_vector_f32()are removed; useset_parameter()instead, andset_parameter_alias()for theirset_parameter_alias_*()variants.- Fix native memory leaks of queries: a built query's native resources were never freed, and neither were the
builder's unless
close()was called. Nowbuild()closes the builder (like in Java and Dart), and a query is closed when it is garbage collected or its store is closed;Query.close()closes it right away, also viawith box.query(...).build() as query:. Using a closed query (or builder) raises anIllegalStateError.
Breaking: a query builder can not be used afterbuild(), e.g. to build a second query. - Fix
offset()andlimit()to return the query (as documented), e.g. forquery.offset(10).limit(5).find(); they returned a C result code (int), so chaining failed. A negative value now raises anIllegalArgumentError(it wrapped around in C, e.g.offset(-1)silently found nothing), a value other than an int aTypeError. - The top-level module now also exports
Query,QueryBuilder,PropertyQueryConditionandHnswFlags
- Property names are now stored in camelCase in the database and the model JSON file, e.g.
dateCreatedfor the attributedate_created, like in the other ObjectBox languages (Java, Kotlin, Dart, Swift). This makes it easy to share the model with them, e.g. for Sync, and shows the same names e.g. in the ObjectBox Admin. Python code is not affected: it still uses the attribute names. Existing databases and model JSON files are updated automatically: the properties are renamed, keeping their data. To store names as written in Python instead, use@Entity(python_names=True).
Breaking: property names in the database and the model JSON file change (only for names with underscores); e.g. queries in the ObjectBox Admin or data in MongoDB (via Sync) use the new names. - Unique constraints, like in Java and Dart: e.g.
email = String(unique=Unique()); putting an object with a value that another object already has raises aUniqueViolatedError. WithUnique(ConflictStrategy.REPLACE), the other object is removed instead (for one property per entity; Sync entities require it for all unique properties). A unique property is indexed (a type can be given viaindex=). - The default index type for String properties is now HASH (was VALUE), like in the other ObjectBox languages; it
takes less space. Other types still default to VALUE. Existing indexes are changed when the store is opened (the data
is kept); to keep a value index, use
Index(IndexType.VALUE)(IndexTypeis now exported by the top-level module).
Breaking: the index of String properties withIndex()changes (e.g. the model JSON file). - Fix
index=Index(the class, as in the@Entitydocumentation): opening the store failed with aTypeError; it now works likeindex=Index(). Other values than anIndexorHnswIndexraise aTypeErrorright away. - Property classes no longer take an
idargument: it was ignored (IDs are assigned via the model JSON file), so passing one now raises aTypeError, as do other unknown arguments (they were ignored, too).uidis now keyword-only, e.g.String(uid=1234).
Breaking: positional arguments, e.g.String(0, 1234)for an ID and a UID, raise aTypeError; useuid=. - To-one relations: the new property type
Relationstores the ID of an object of the target entity, e.g.customer_id = Relation(Customer)(orRelation("Customer"), e.g. to refer to the entity itself). Values are plain ints (0 for none); unlike anInt64, ObjectBox knows the target, e.g. removing the target object resets the relation to 0. It has an index (like the other ObjectBox languages, without zero values) and the usual integer conditions. Name it with an_idsuffix, e.g.customer_id: it is stored ascustomerId, like the target ID of aToOnenamedcustomerin Java, Kotlin and Dart, so models and data match (e.g. for Sync). An existingInt64property cannot be changed to aRelation(different type); add a new property instead. - Fix putting an object with an ID that ObjectBox did not assign (e.g.
MyEntity(id=3)in an empty box): this was accepted, but a new object put later could get the same ID and silently replaced it. Now it raises anIllegalArgumentError("ID is higher or equal to internal ID sequence"), like in the other ObjectBox languages. To use your own IDs, declare the ID property asId(assignable=True); new objects (ID 0) then get an ID higher than any ID put before.
Breaking: code that puts objects with made-up IDs needsId(assignable=True). - Box: putting a list (or a tuple, or several objects as arguments) returns their IDs, like Dart's
putMany()(returned None before; the IDs are still set on the objects) - Box: new method
remove_many(ids_or_objects), removing in one transaction and returning the number removed (like Dart'sremoveMany());remove()of a list raises aTypeErrorpointing to it (failed in ctypes before) - Box: new methods
contains(id),get_many(ids)andupdate(object)(the latter raises if the object does not exist yet, i.e. unlikeput()it never inserts) - Putting objects with
Nonevalues: a property set toNoneis stored as null (left out of the stored object), where putting failed before. Reading is unchanged: a null value reads back as the property's default (e.g."",0), as any property missing from a stored object. Properties that are not set at all are still stored with their default. Exceptions:Noneinside a vector or list still fails;Flexproperties storeNoneas a value (as before). - Fix
Int8Listproperties: putting and reading objects failed. Values are lists of signed 8-bit integers (-128 to 127). - Fix reading list properties (e.g.
Int32List) that are not present in a stored object, e.g. added to the entity after the object was put: this failed instead of returning an empty list. - Fix reading
DateNano(py_type=float)properties: they read back nanoseconds (as an int) instead of seconds (float), unlikeDate(py_type=float)and how float values are put.
Breaking: code reading such properties gets seconds now. - Fix reading
Date/DateNanoproperties withdatetimevalues, vector properties with numpy arrays (e.g.Float32Vector) andFlexproperties that are not present in a stored object (e.g. added to the entity after the object was put): this failed with aTypeError. They read as the default now, like unset properties: the epoch (UTC), an empty array andNone.
- New module
objectbox.syncwith a Sync client (Sync.client(),SyncClient) to synchronize data with an ObjectBox Sync server (which stores your data centrally with optional sync to MongoDB). The top-level module exports its API, too (e.g.from objectbox import Sync, SyncCredentials), importingobjectbox.syncon first use only @SyncEntity()enables Sync for an entity (optionally with shared global IDs);SyncClockandSyncPrecedenceproperties for Sync conflict resolution.- Client options include Sync filter variables, flags (
SyncFlags) and certificate paths. - Listeners for login, connection, completed, change and server time events.
- Requires the Sync variant of the C library: install the new package
objectbox-syncinstead ofobjectbox(pip install objectbox-sync; it installsobjectbox-apiandobjectbox-sync-clib, the Sync variant of the library).
- All exceptions raised by the database now derive from
DbError, with a specific subclass per error, e.g.IdNotFoundError,UniqueViolatedErrororDbFullError(see moduleobjectbox.exceptions).IllegalArgumentErroris also aValueError,IllegalStateErrorandFeatureNotAvailableErrorare also aRuntimeError,TimeoutErroris also the built-inTimeoutError, so they can be caught as the matching built-in exception, too. Like in Java and Dart,UniqueViolatedErroris aConstraintViolatedErrorandFilePagesCorruptErroraFileCorruptError. (5.0 betas namedFeatureNotAvailableErrorFeatureNotAvailable; that name still works.)
Breaking: this replacesCoreExceptionandNotFoundException(the latter was exported by the top-level module). - The top-level module exports the exceptions applications handle most, e.g.
from objectbox import UniqueViolatedError, IllegalStateError(like Dart); all are inobjectbox.exceptions - Wrong arguments raise a
TypeErroror anIllegalArgumentError(aValueError) with a clear message, instead of a plainException, anAttributeError, aKeyErroror actypes.ArgumentError, e.g. putting an object of another entity,remove("1"), a condition value of the wrong type (Int64.equals("1")), an unknown property name or a non-entity passed toBoxorModel.entity(). An error code of the C library unknown to Python is aDbErrorwith that code (was a plainException).
Breaking: code catching the previous exception types, e.g.Store(model=42)raises aTypeError(was aValueError).
- Fix
DebugFlags.LOG_QUERIES: its value was 3 (logging read and write transactions) instead of 4 (logging queries). New flagsLOG_EXCEPTION_STACK_TRACEandRUN_THREADING_SELF_TEST. - Log callback (
log_callbackoption): the log level values passed to the callback changed (seeLogLevel, now alsoTrace); code comparing againstLogLevelmembers is not affected. - A store that is no longer referenced is now closed right away (was: only when Python's cyclic GC ran);
e.g. on Windows, reopening the same directory could fail with "another store is still open using the same path".
Still, prefer closing stores explicitly via
close(). store.box(entity)returns the sameBoxper entity (like Java and Dart) instead of creating one per call (~1 µs); e.g.store.box(Task).get(id)per operation is ~20% faster,store.box(Task).count()~35%- Stores can be used in a
withstatement, which closes the store at the end (with Store(...) as store:); newStore.is_closed() - Using a closed store raises an
IllegalStateError("Store is closed"), also via its boxes and queries; before, errors were misleading (e.g.IllegalArgumentError'Argument "store" must not be null') and a box obtained before closing used the freed native store (the C library reported "Irrecoverable memory error"). - Fix the
model_bytesandmodel_bytes_directoptions (failed with aTypeError) - Fix using a model JSON file with an empty
lastIndexId(failed with aValueError), e.g. one shared with an app using ObjectBox C++ or Go, which write it like this if there is no index - Model JSON file: removed entities, properties and indexes are recorded as retired UIDs, which are not used again
(a
uidof one is rejected), like in the other ObjectBox languages. Writing the file keeps what Python does not manage, e.g. for a file shared with another ObjectBox language: relations of other languages (e.g. ToMany) and fields likelastRelationId; the file now also hasmodelVersion. This fixes files that the ObjectBox Generator (C++, Go) rejected, e.g. after removing an entity or property in Python. - Model: the index of an existing property can now be added, removed or changed (e.g. its type), and so can other property flags; this failed with "mismatches property found in JSON file" before. The data is kept.
- API reference documentation (docstrings for the public API; generated via Sphinx)
- Example "tasks": added a command to remove tasks and improved the command line
- ObjectBox now supports vector search ("vector database") to enable efficient similarity searches. This is particularly useful for AI/ML/RAG applications, e.g. image, audio, or text similarity. Other use cases include sematic search or recommendation engines. See https://docs.objectbox.io/ann-vector-search for details.
- The definition of entities (aka the data model) is now greatly simplified
- Type-specific property classes, e.g.
name: String,count: Int64,score: Float32 - Automatic ID/UID and model management (i.e. add/remove/rename of entities and properties)
- Automatic discovery of @Entity classes
- Type-specific property classes, e.g.
- Queries: property-based conditions, e.g.
box.query(City.name.starts_with("Be")) - Queries: logical operators, e.g.
box.query(City.name == "Berlin" | City.name == "Munich") - Convenient "Store" API (deprecates ObjectBox and Builder API)
- New examples added, illustrating an VectorSearch and AI/RAG application
- Stable flat public API provided by single top-level module objectbox
- Dependency flatbuffers: Updated to 24.3.50
- Adjusting the version number to match the core version (4.0); we will be aligning on major versions from now on.
Please check https://github.com/objectbox/objectbox-python/releases for details.