This file provides guidance to AI coding agents when working with code in this repository.
All make targets use (and create if needed, via python3 -m venv) the virtual environment in .venv from requirements.txt.
make depend— set up venv and download the ObjectBox C shared libraries (download-c-lib.py) intoobjectbox_clib/lib/<arch>/. Required once after checkout and whenever the C library version changes.make depend-sync— download the Sync variant of the C libraries intoobjectbox_sync_clib/lib/<arch>/; if present, it is loaded instead (needed for the Sync tests).make test— run all tests (python -m pytest --capture=no --verbose)make build— clean and build the wheels intodist/:objectbox-clib(one per platform),objectbox-apiand theobjectboxmetapackage;make build-syncadds theobjectbox-sync-clibwheels and theobjectbox-syncmetapackagemake test-fast-flatbuffers-builder— all tests with the experimental fast FlatBuffers builder in verify mode (pytest --fast-flatbuffers-builderorOBX_TEST_FAST_FLATBUFFERS_BUILDER=1): every object put by any test is compared byte by byte with the referencemake test-flatbuffers-codegen— the same with the experimental FlatBuffers codegen (pytest --flatbuffers-codegenorOBX_TEST_FLATBUFFERS_CODEGEN=1)make benchmark— CRUD benchmark (benchmark.py;BENCHMARK_ARGS=--fast-flatbuffers-builderor--flatbuffers-codegenfor the experimental builders)- Single test:
.venv/bin/python -m pytest tests/test_query.py::test_name -s -v(the venv gets pytest etc. fromrequirements.txtvia anymaketarget, e.g.make depend)
Run pytest from the repository root: tests import both common (via tests/conftest.py) and tests.model.
There is no linter configured; CONTRIBUTING.md asks for PEP-8 formatting.
Python binding for the ObjectBox C API (objectbox-c) via ctypes; objects are serialized with FlatBuffers.
objectbox/c.py— all C bindings. Loads the library at import from theobjectbox_sync_clibpackage if it has one for the platform, else fromobjectbox_clib(lib/<platform.machine()>/, ormacos-universal;lib_package(); ImportError if neither has one) and asserts the core version equalsrequired_version.required_versionis the single source of the C library version (see Packaging). Functions are declared with helpers:c_fn(checks result/pointer),c_fn_rc(returnsobx_err, raises viacheck_obx_err),c_fn_qb_cond,c_fn_nocheck.objectbox/exceptions.py—DbErrorbase class plus one subclass perDbErrorCode;create_db_error(code)maps C error codes to exception classes (message taken fromobx_last_error_message()). Python code raises them with a message too, e.g.IllegalArgumentError("...")(also aValueError) for invalid values andIllegalStateError("...")(also aRuntimeError) e.g. for closed objects;TypeErrorfor wrong types.- Model definition (
objectbox/model/):@Entity()(entity.py) wraps a user class into an_Entity(handles FlatBuffers_marshal/_unmarshal;_marshal_builderis the genericflatbuffers.Builderpath) and registers it in the globalobx_models_by_nameunder a model name (default"default"). Property classes used as bare class members (e.g.name = String) are instantiated by the decorator. A property'snameis the Python attribute name (used by all Python code);_db_nameis its name in the database and model JSON: camelCase (camel_case(), e.g.dateCreated) unless@Entity(python_names=True).properties.py— typedPropertyclasses (scalars, vectors/lists, Date, Flex,Relation(to-one: an int ID with the target entity; created viaobx_model_property_relation,relationTargetin the model JSON),HnswIndexfor vector search;Index/Uniquearguments are checked byProperty._index_arg()/_unique_arg(),Property._check_args()rejects unknown arguments). Checks needing the whole model (relation targets, unique REPLACE rules) are inModel._validate(). Property operators/methods (==,starts_with,nearest_neighbors, …) producePropertyQueryConditions.fast_builder.py— experimental (objectbox/experimental.py, off by default):FastBuilderproduces the same bytes as_marshal_builderwith the layout precomputed per entity (by alignment residue); it mirrors the Builder's layout rules, so keep it byte-identical (tests/test_fast_flatbuffers_builder.py). A property set toNoneis left out of the object (null; the reference skips it), so objects withNonevalues go throughmarshal_with_nulls()(layout per null combination); reading still returns defaults for missing fields.codegen.py— experimental, the alternative tofast_builder.py(only one is active:experimental._flatbuffers_mode):CodegenBuildergenerates andexecs a marshal function per entity that does whatFastBuilder.marshaldoes, unrolled; it uses aFastBuilder's layouts. Changes toFastBuilder.marshalmust be mirrored in the generated code; the same tests check both. It also generates an unmarshal function, used by_Entity._unmarshalin codegen mode, that must return the same values as_unmarshal_table(flatbuffers.Table, the reference), so changes to reading go into both. Each function is compiled at its first use; withcache_dir(enable_flatbuffers_codegen), via a .py file in that directory (named by the source hash) and Python's bytecode cache.idsync.py— assigns IDs/UIDs by syncing the model against anobjectbox-model.jsonfile (read, matched by UID then name, rewritten from scratch). This is how renames/removals are tracked across schema changes. Matching by name uses_db_name, falling back to the other form of the name (e.g. the Python name stored before 5.0), which renames the property and keeps its data. JSON entries not matched (removed) are retired (retired*Uids; also never generated or accepted as user UIDs); fields Python does not manage (standalonerelationsof other languages,lastRelationId,lastSequenceId,modelVersion,version) are copied from the loaded JSON; unassigned "last" IDs are written like the loaded file (""for objectbox-generator files, else"0:0").
Store(store.py) — opening a store: resolve the model (aModelor a model name string → collect registered entities), locateobjectbox-model.json(explicitmodel_json_file, else the calling module's directory found via the call stack, else__main__'s directory, else CWD), runsync_model(only once perModeland JSON file in a process:Model._synced_json_file; models for names are cached in_models_by_name), then buildStoreOptionsand callobx_store_open. Once closed,Store._c_store(a property; the C store is in_c_store_or_none) andBox._c_boxraiseIllegalStateError("Store is closed").skip_model_sync=Trueopens an existing store without passing the model (schema from the database). Directory prefixmemory:opens an in-memory DB.Box(box.py) — CRUD per entity.box.query(condition)returns aQueryBuilder; conditions (condition.py, combinable with&/|) are applied onto the C query builder, then.build()yields aQueryand closes the builder. Queries and builders free their C objects viaclose()(also__del__; aQueryis a context manager); the store tracks the open ones (Store._native_resources, a WeakSet) and closes them before the C store; once closed,_c_query/_c_builderraiseIllegalStateError.Query.set_parameter(s)()choose the C function by the property type (_parameter_functionsinquery.py); forset_parameter(s)_alias(),QueryBuilderrecords the entity and property of each condition alias (alias()uses the property of the last condition, which the condition methods record via_condition_property_id();order()does not).QueryBuilder.link()/backlink()return a builder of the linked entity (_linked(): shares the C query builder's lifetime and the aliases;build(),order()andclose()raise on it); parameters of linked conditions use the property's entity (prop._entity, set by@Entity()).- Sync (
sync.py) —SyncClient(created only viaSync.client(); its constructor raises) wraps the C sync client (obx_sync_*; ctypes callbacks passed to C, also replaced ones, are kept referenced until the client is closed). The store keeps its open client alive (Store.sync_client(), like Java and Dart) and closes it (close listener) before the store is closed.@SyncEntity()sets the entity's Sync flags. - Deprecated API:
ObjectBoxandBuilderare thin wrappers aroundStorekept for backward compatibility (tests/test_deprecated.py). - numpy and flatbuffers (which imports numpy) are imported lazily, only where needed (vector/flex properties, the reference FlatBuffers paths); no module-level imports of either (
tests/test_lazy_imports.py). Useis_ndarray()(c.py) for type checks andprop._fb_format(struct format) instead ofprop._fb_typeoutside the reference paths. - Public API is the flat export list in
objectbox/__init__.py(the Sync names via a module__getattr__, importingobjectbox.syncon first use: it takes a few ms); the binding version (version = Version(...)) is also defined there and used bysetup.pyandsetup-meta.py(viabuild_info.py).
Five pip packages are built from this repository:
objectbox-api(setup.py): the Python binding (import packageobjectbox); no C library dependency.objectbox(setup-meta.py): metapackage without code, the one users install; depends onobjectbox-api(exact binding version) andobjectbox-clib(==<C version>.*). Its PyPI description isREADME.md.objectbox-sync(setup-meta.py --sync): metapackage likeobjectbox, but depends onobjectbox-sync-clibinstead ofobjectbox-clib. Sync users install it instead ofobjectbox.objectbox-clib(setup-clib.py, packageobjectbox_clib/): only the C libraries, no Python code. Built as one wheel per platform (wheel platform tags inPLATFORMS, so pip picks the platform's library). There is no fallback wheel: other platforms, e.g. Linux armv6l (no manylinux support), are not supported. Versioned like the C library.objectbox-sync-clib(setup-clib.py --sync, packageobjectbox_sync_clib/): likeobjectbox-clib, but the Sync variant of the C libraries.objectbox/c.py(lib_package()) prefers its library overobjectbox_clib's (e.g. if both metapackages are installed).
Upgrade caveat: up to 4.x, the distribution objectbox contained the code. An in-place pip install -U objectbox from such a version breaks the installation (pip removes the old objectbox files, now owned by objectbox-api, after installing it); README.md and CHANGELOG.md document uninstalling first. make publish uploads the C libraries first, then objectbox-api, then the metapackages.
build_info.py reads the binding version (objectbox/__init__.py) and the C library version (required_version in objectbox/c.py) as text for the build scripts and download-c-lib.py; it also holds the metadata shared by setup.py and setup-meta.py. Build scripts must not import objectbox: that loads the C library. To bump the C library, only change required_version, run make depend, and re-check the platform tags in setup-clib.py against the new libraries' minimum glibc/macOS versions.
tests/model.pydefines test entities;tests/common.pyhascreate_test_store()which deletes the DB dir (testdataby default) andtests/objectbox-model.jsonbefore opening a fresh store. Thetest_storefixture inconftest.pywraps it.- Sync tests (
tests/test_sync.py, markersync) run only if the loaded library supports Sync (make depend-sync). Tests needing a Sync server useOBX_SYNC_SERVER_URL(a running server with the modeltests/objectbox-model-sync.json, e.g. via Docker withtests/sync_server_config.json), or download and start it in GitLab CI; otherwise they are skipped. - Tests write DB directories and model JSON files (e.g.
testdata/,tests/test-model*.json) into the working tree; these are artifacts, not fixtures to commit.
GitHub Actions (.github/workflows/test.yaml) runs make depend && make test && make test-fast-flatbuffers-builder && make test-flatbuffers-codegen on Linux/Windows/macOS for Python 3.7–3.14 (plus Windows ARM64 with Python 3.12), so code must stay compatible with Python 3.7; its job test-sync runs make depend-sync && make test with OBX_TEST_REQUIRE_SYNC=1 (fails if the Sync library is not loaded; Sync server tests skip) on the same OSes. GitLab CI (.gitlab-ci.yml) builds the wheels with make depend test build on Linux x64, then tests the built objectbox wheel (also with --fast-flatbuffers-builder and --flatbuffers-codegen; installed in place of the source packages, which are deleted; the platform's objectbox-clib wheel is picked via --find-links dist) on Linux x64 (Python 3.7–3.12 Docker images), Linux armv7hf and aarch64, macOS (arm64 runner; the macOS library is universal) and Windows x64.