feat: Add CSV ingestion support for Frames - #745
Conversation
Closes #70 This adds which subclasses and registers it with the CLI so can use .
There was a problem hiding this comment.
Pull request overview
This PR extends the Python SDK’s CSV ingestion pipeline to support Frame objects by introducing a FrameDataCSV adapter over the existing image ingestion logic, and wiring the new ingest type into the CLI.
Changes:
- Allow
ImageDataCSVsubclasses to override the command used in CSVParser-generated queries (AddImagevsAddFrame). - Add
FrameDataCSVfor frame ingestion via CSV. - Register
IngestType.FRAMEin theadb ingest from-csvCLI mapping.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| aperturedb/ImageDataCSV.py | Makes the ingestion command overrideable (enables reuse for Frames). |
| aperturedb/FrameDataCSV.py | Adds a new CSV ingester for Frames (needs _Frame index override). |
| aperturedb/cli/ingest.py | Registers FRAME ingest type in CLI (currently missing BBoxDataCSV import). |
Suppressed comments (1)
aperturedb/cli/ingest.py:205
BBoxDataCSVis referenced iningest_typesbut is no longer imported in this function, which will raiseNameErrorwhenfrom_csvruns (even for non-bounding-box ingest types, since the dict is constructed unconditionally). Re-add the missing import.
from aperturedb.ImageDataCSV import ImageDataCSV
from aperturedb.FrameDataCSV import FrameDataCSV
from aperturedb.EntityDataCSV import EntityDataCSV
from aperturedb.BlobDataCSV import BlobDataCSV
from aperturedb.ConnectionDataCSV import ConnectionDataCSV
from aperturedb.PolygonDataCSV import PolygonDataCSV
from aperturedb.VideoDataCSV import VideoDataCSV
from aperturedb.DescriptorDataCSV import DescriptorDataCSV
from aperturedb.DescriptorSetDataCSV import DescriptorSetDataCSV
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
luisremis
left a comment
There was a problem hiding this comment.
add testing to the new FrameDataCSV.
add Frame to the OM, analogous to how Image and Video work.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (2)
aperturedb/cli/ingest.py:199
from_csv()builds theingest_typesdict usingBBoxDataCSV(forIngestType.BOUNDING_BOX) but the import was removed. This will raise aNameErrorwhen invokingadb ingest from-csvwith--ingest-type BOUNDING_BOX. Re-add the missing import alongside the other CSV parsers.
from aperturedb.ImageDataCSV import ImageDataCSV
from aperturedb.FrameDataCSV import FrameDataCSV
from aperturedb.EntityDataCSV import EntityDataCSV
from aperturedb.BlobDataCSV import BlobDataCSV
aperturedb/FrameDataCSV.py:14
- New
FrameDataCSVbehavior (overriddencommand+_Frameindices) is not covered by tests. There are existing tests exercising other CSV ingesters (e.g.,test/test_SPARQL.pyusesImageDataCSVandEntityDataCSV), so adding at least a small unit test forFrameDataCSVwould help prevent regressions (e.g., verifyingcommand == "AddFrame"andget_indices()targets_Frame).
class FrameDataCSV(ImageDataCSV):
def __init__(self, *args, **kwargs):
self.command = "AddFrame"
super().__init__(*args, **kwargs)
def get_indices(self):
return {
"entity": {
"_Frame": self.get_indexed_properties()
}
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
aperturedb/Images.py:1004
Framesused to be defined inaperturedb/Images.py; removing it from this module is a breaking change for any code that doesfrom aperturedb.Images import Frames. If the intent is just to move the implementation, consider adding a backwards-compatible shim (e.g., module__getattr__) that resolvesFrameslazily fromaperturedb.Frames.
return return_dictionary
test/test_FrameDataCSV.py:6
pytestandpandasare imported but never used in this test, which adds unnecessary dependencies and can trigger unused-import lint failures.
import pytest
import pandas as pd
import tempfile
import os
from aperturedb.FrameDataCSV import FrameDataCSV
aperturedb/FrameDataCSV.py:8
FrameDataCSVonly needs to override the command string; defining a variadic__init__drops the base-class signature and makes introspection/type checking harder. WithImageDataCSVnow usinggetattr(self, "command", ...), you can setcommandas a class attribute and remove the custom initializer.
class FrameDataCSV(ImageDataCSV):
def __init__(self, *args, **kwargs):
self.command = "AddFrame"
super().__init__(*args, **kwargs)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (3)
aperturedb/Images.py:1004
Framesused to be defined in this module; removing it breaks existing user code that importsFramesviafrom aperturedb.Images import Frames. Consider re-exporting the new implementation from this module to preserve backward compatibility while keeping the real class inaperturedb/Frames.py.
return return_dictionary
aperturedb/FrameDataCSV.py:8
FrameDataCSVis a new public CSV-ingestion helper but it lacks the class-level docstring that other*DataCSVclasses provide (e.g.BBoxDataCSV). Adding a short docstring helps generated docs and keeps the module consistent.
class FrameDataCSV(ImageDataCSV):
def __init__(self, *args, **kwargs):
self.command = "AddFrame"
super().__init__(*args, **kwargs)
test/test_FrameDataCSV.py:5
pytestandpandasare imported but never used in this test module; removing unused imports keeps the test lightweight and avoids unnecessary dependency coupling.
import pytest
import pandas as pd
import tempfile
import os
from aperturedb.FrameDataCSV import FrameDataCSV
…s, backward-compat shim)
|
Addressed the latest Copilot reviewer comments in commit cce1bc7 (added backward-compatibility shim for |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (3)
test/test_FrameDataCSV.py:7
- This line is long enough that autopep8 will likely reformat it; in this repo CI enforces autopep8 formatting, so it’s better to wrap it now to avoid formatting-only CI failures.
with tempfile.NamedTemporaryFile(suffix=".csv", mode="w", delete=False) as f:
aperturedb/Frames.py:21
- This
super().__init__call exceeds typical autopep8 line wrapping; wrapping it avoids formatting-only CI failures and keeps it consistent with the rest of the codebase’s style.
def __init__(self, client, batch_size=100, response=None, **kwargs):
super().__init__(client, batch_size=batch_size, response=response, **kwargs)
aperturedb/DataModels.py:76
- Changing
FrameDataModelfromIdentityDataModeltoBlobDataModelmakesurla required field (viaBlobDataModel). If any callers constructFrameDataModelwithout a URL today, this is a breaking API change. If this is intended, it should be called out in the PR description/changelog; if not intended, consider keepingIdentityDataModelor makingurloptional onFrameDataModel.
class FrameDataModel(BlobDataModel):
"""Frame data model for ApertureDB.
"""
type = ObjectType.FRAME
5faa0c7 to
0b77b04
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aperturedb/DataModels.py:7
Optionalis imported but not used anywhere in this module, which adds noise and may trip linters/static checks. Remove it from the import list.
from typing import ClassVar, Optional
- Added `test_Frames.py` to verify the `Frames` object model correctly maps to `ObjectType.FRAME`, addressing the reviewer's request. - Removed unused `Optional` import from `DataModels.py` as suggested by Copilot.
|
Addressed feedback from @luisremis:
Updated in commit f393910 |
|
Addressed feedback from @copilot-pull-request-reviewer[bot]:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aperturedb/Images.py:1011
- The
__getattr__shim makesfrom aperturedb.Images import Frameswork again, butfrom aperturedb.Images import *will no longer exportFramesbecause the name isn’t in the module namespace. If the intent is full backward compatibility, add a module__all__that mirrors the default export set and appendsFrames, letting import-star retrieve it via__getattr__without reintroducing the circular import.
def __getattr__(name: str):
if name == "Frames":
from aperturedb.Frames import Frames
return Frames
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
Addressed the latest Copilot reviewer comment in commit c560a56 by explicitly defining |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (1)
aperturedb/Images.py:1011
__getattr__returnsFramesbut doesn’t cache it into the module namespace. This means everyImages.Framesaccess re-runs the import lookup anddir(aperturedb.Images)won’t showFrames, which is a small but real backward-compatibility regression compared to the previous in-moduleFramesclass. Cache the resolved class inglobals()before returning it (same pattern used inaperturedb/transformers/__init__.py).
def __getattr__(name: str):
if name == "Frames":
from aperturedb.Frames import Frames
return Frames
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Addressed Copilot review comment to cache the resolved class in globals() before returning it to improve backward compatibility and avoid re-imports.
|
Addressed the latest Copilot reviewer comment in commit 83878fd by caching the lazily-imported |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated no new comments.
Suppressed comments (4)
aperturedb/Images.py:1012
- The backward-compatibility behavior is now implemented via module-level
__getattr__, but there’s no test covering the previously supported import path (e.g.,from aperturedb.Images import Frames). Add a test that importsFramesfromaperturedb.Imagesand asserts it resolves toaperturedb.Frames.Frames(and ideally that it’s cached on the module after first access) to prevent regressions.
# Shim for backward compatibility
def __getattr__(name: str):
if name == "Frames":
from aperturedb.Frames import Frames
globals()[name] = Frames
return Frames
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
aperturedb/FrameDataCSV.py:16
- The entity name is hard-coded as
\"_Frame\". To avoid drift if naming/constants change elsewhere (and to align with the rest of the code usingObjectType), prefer deriving this from the shared enum/constant (e.g.,ObjectType.FRAME.value) rather than duplicating the string literal.
def get_indices(self):
return {
"entity": {
"_Frame": self.get_indexed_properties()
}
}
aperturedb/DataModels.py:75
- Changing
FrameDataModel’s base class fromIdentityDataModeltoBlobDataModelchanges the model’s inherited fields/validation and can break callers constructingFrameDataModelwith the old schema. Please document the expected fields for frames (now that it’s blob-backed) and add/adjust a unit test that validates the accepted/required fields forFrameDataModelto lock down the intended public contract.
class FrameDataModel(BlobDataModel):
"""Frame data model for ApertureDB.
"""
type = ObjectType.FRAME
test/test_FrameDataCSV.py:25
- Calling
f.close()inside awith NamedTemporaryFile(...) as f:block is redundant (the context manager will close the file). Removing the explicit close improves readability and avoids confusion about file lifecycle.
with tempfile.NamedTemporaryFile(
suffix=".csv", mode="w", delete=False
) as f:
f.write("url,id\nhttp://example.com/frame.jpg,1\n")
f.close()
|
Addressed the latest review feedback in commit 087f510:
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (2)
aperturedb/ImageDataCSV.py:202
getattr(self, "command", "AddImage")will preserve an explicitly-setcommand=None(or empty string), resulting in an invalid command string later. Consider falling back when the retrieved value is falsy (e.g., use"AddImage"when the subclass/instance didn’t provide a usable command). This keeps the new subclass-override behavior while avoidingNoneleaking into ingestion logic.
self.relative_path_prefix = os.path.dirname(self.filename) \
if self.source_type == HEADER_PATH and self.blobs_relative_to_csv else ""
self.command = getattr(self, "command", "AddImage")
aperturedb/Images.py:1015
- Building
__all__dynamically fromglobals()can unintentionally export internal/imported symbols and can also produce duplicate entries (e.g.,"Frames"could appear twice after it’s cached intoglobals()). Prefer defining__all__explicitly (or deduplicating and limiting it to the intended public API) sofrom aperturedb.Images import *remains stable and doesn’t accidentally surface non-API names.
# Shim for backward compatibility
def __getattr__(name: str):
if name == "Frames":
from aperturedb.Frames import Frames
globals()[name] = Frames
return Frames
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__ = [k for k in globals().keys() if not k.startswith('_')] + ['Frames']
Closes #70
This PR adds a
FrameDataCSVclass which simply inherits fromImageDataCSVand overrides the command toAddFrame. It also registersIngestType.FRAMEin the CLI, closing the loop on Frame OM and ingestion support without duplicating the complex loading/validation logic of Images.