working aligned dynamic table and TimeSeriesReferenceVectorData

This commit is contained in:
sneakers-the-rat 2024-08-12 22:57:00 -07:00
parent dd99ac24eb
commit 06a18c23a8
Signed by untrusted user who does not match committer: jonny
GPG key ID: 6DCB96EF1E4D232D
9 changed files with 307 additions and 59 deletions

View file

@ -5,7 +5,7 @@
groups = ["default", "dev", "plot", "tests"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
content_hash = "sha256:903c3aeebf0fb234263b45213693f0eaee7ac290d22633b1d7a4d5aff51d032b"
content_hash = "sha256:ed633a147948a9923f6b3a99690d5d8bad0b4b8c0d528abe62d132b05d1d9f39"
[[metadata.targets]]
requires_python = ">=3.10,<3.13"
@ -1036,7 +1036,7 @@ files = [
[[package]]
name = "numpydantic"
version = "1.3.0"
version = "1.3.1"
requires_python = "<4.0,>=3.9"
summary = "Type and shape validation and serialization for numpy arrays in pydantic models"
groups = ["default"]
@ -1046,8 +1046,8 @@ dependencies = [
"typing-extensions>=4.11.0; python_version < \"3.11\"",
]
files = [
{file = "numpydantic-1.3.0-py3-none-any.whl", hash = "sha256:bda3aa2cd858e9211006be8b8e589e1905b2c6a2db17cec0c28563ba1ad66b68"},
{file = "numpydantic-1.3.0.tar.gz", hash = "sha256:b3931d51ba7e22d48bdd2ae56cad368f63db99ef74e8570021a7fd176b2ffc1f"},
{file = "numpydantic-1.3.1-py3-none-any.whl", hash = "sha256:c0a37c093fcd0e4ed52c4556f4e804eec76fcf924c546e475509e662336f9f61"},
{file = "numpydantic-1.3.1.tar.gz", hash = "sha256:d61868d7912f2dfee9906bd989399d74f470dee10d5028409c2f5d39529fc4af"},
]
[[package]]
@ -1831,29 +1831,29 @@ files = [
[[package]]
name = "ruff"
version = "0.5.6"
version = "0.5.7"
requires_python = ">=3.7"
summary = "An extremely fast Python linter and code formatter, written in Rust."
groups = ["dev"]
files = [
{file = "ruff-0.5.6-py3-none-linux_armv6l.whl", hash = "sha256:a0ef5930799a05522985b9cec8290b185952f3fcd86c1772c3bdbd732667fdcd"},
{file = "ruff-0.5.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b652dc14f6ef5d1552821e006f747802cc32d98d5509349e168f6bf0ee9f8f42"},
{file = "ruff-0.5.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:80521b88d26a45e871f31e4b88938fd87db7011bb961d8afd2664982dfc3641a"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9bc8f328a9f1309ae80e4d392836e7dbc77303b38ed4a7112699e63d3b066ab"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4d394940f61f7720ad371ddedf14722ee1d6250fd8d020f5ea5a86e7be217daf"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111a99cdb02f69ddb2571e2756e017a1496c2c3a2aeefe7b988ddab38b416d36"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e395daba77a79f6dc0d07311f94cc0560375ca20c06f354c7c99af3bf4560c5d"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c476acb43c3c51e3c614a2e878ee1589655fa02dab19fe2db0423a06d6a5b1b6"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e2ff8003f5252fd68425fd53d27c1f08b201d7ed714bb31a55c9ac1d4c13e2eb"},
{file = "ruff-0.5.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c94e084ba3eaa80c2172918c2ca2eb2230c3f15925f4ed8b6297260c6ef179ad"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1f77c1c3aa0669fb230b06fb24ffa3e879391a3ba3f15e3d633a752da5a3e670"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f908148c93c02873210a52cad75a6eda856b2cbb72250370ce3afef6fb99b1ed"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:563a7ae61ad284187d3071d9041c08019975693ff655438d8d4be26e492760bd"},
{file = "ruff-0.5.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:94fe60869bfbf0521e04fd62b74cbca21cbc5beb67cbb75ab33fe8c174f54414"},
{file = "ruff-0.5.6-py3-none-win32.whl", hash = "sha256:e6a584c1de6f8591c2570e171cc7ce482bb983d49c70ddf014393cd39e9dfaed"},
{file = "ruff-0.5.6-py3-none-win_amd64.whl", hash = "sha256:d7fe7dccb1a89dc66785d7aa0ac283b2269712d8ed19c63af908fdccca5ccc1a"},
{file = "ruff-0.5.6-py3-none-win_arm64.whl", hash = "sha256:57c6c0dd997b31b536bff49b9eee5ed3194d60605a4427f735eeb1f9c1b8d264"},
{file = "ruff-0.5.6.tar.gz", hash = "sha256:07c9e3c2a8e1fe377dd460371c3462671a728c981c3205a5217291422209f642"},
{file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"},
{file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"},
{file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"},
{file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"},
{file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"},
{file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"},
{file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"},
{file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"},
{file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"},
]
[[package]]
@ -2090,44 +2090,44 @@ files = [
[[package]]
name = "watchdog"
version = "4.0.1"
version = "4.0.2"
requires_python = ">=3.8"
summary = "Filesystem events monitoring"
groups = ["default"]
files = [
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:da2dfdaa8006eb6a71051795856bedd97e5b03e57da96f98e375682c48850645"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e93f451f2dfa433d97765ca2634628b789b49ba8b504fdde5837cdcf25fdb53b"},
{file = "watchdog-4.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0107bbb6a55f5be727cfc2ef945d5676b97bffb8425650dadbb184be9f9a2b"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17e32f147d8bf9657e0922c0940bcde863b894cd871dbb694beb6704cfbd2fb5"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03e70d2df2258fb6cb0e95bbdbe06c16e608af94a3ffbd2b90c3f1e83eb10767"},
{file = "watchdog-4.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123587af84260c991dc5f62a6e7ef3d1c57dfddc99faacee508c71d287248459"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:093b23e6906a8b97051191a4a0c73a77ecc958121d42346274c6af6520dec175"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:611be3904f9843f0529c35a3ff3fd617449463cb4b73b1633950b3d97fa4bfb7"},
{file = "watchdog-4.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:62c613ad689ddcb11707f030e722fa929f322ef7e4f18f5335d2b73c61a85c28"},
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0144c0ea9997b92615af1d94afc0c217e07ce2c14912c7b1a5731776329fcfc7"},
{file = "watchdog-4.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:998d2be6976a0ee3a81fb8e2777900c28641fb5bfbd0c84717d89bca0addcdc5"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:dddba7ca1c807045323b6af4ff80f5ddc4d654c8bce8317dde1bd96b128ed253"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:4513ec234c68b14d4161440e07f995f231be21a09329051e67a2118a7a612d2d"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_i686.whl", hash = "sha256:4107ac5ab936a63952dea2a46a734a23230aa2f6f9db1291bf171dac3ebd53c6"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:6e8c70d2cd745daec2a08734d9f63092b793ad97612470a0ee4cbb8f5f705c57"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:f27279d060e2ab24c0aa98363ff906d2386aa6c4dc2f1a374655d4e02a6c5e5e"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:f8affdf3c0f0466e69f5b3917cdd042f89c8c63aebdb9f7c078996f607cdb0f5"},
{file = "watchdog-4.0.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ac7041b385f04c047fcc2951dc001671dee1b7e0615cde772e84b01fbf68ee84"},
{file = "watchdog-4.0.1-py3-none-win32.whl", hash = "sha256:206afc3d964f9a233e6ad34618ec60b9837d0582b500b63687e34011e15bb429"},
{file = "watchdog-4.0.1-py3-none-win_amd64.whl", hash = "sha256:7577b3c43e5909623149f76b099ac49a1a01ca4e167d1785c76eb52fa585745a"},
{file = "watchdog-4.0.1-py3-none-win_ia64.whl", hash = "sha256:d7b9f5f3299e8dd230880b6c55504a1f69cf1e4316275d1b215ebdd8187ec88d"},
{file = "watchdog-4.0.1.tar.gz", hash = "sha256:eebaacf674fa25511e8867028d281e602ee6500045b57f43b08778082f7f8b44"},
{file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"},
{file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"},
{file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"},
{file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"},
{file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"},
{file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"},
{file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"},
{file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"},
{file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"},
{file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"},
{file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"},
{file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"},
{file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"},
{file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"},
{file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"},
{file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"},
]
[[package]]
name = "webcolors"
version = "24.6.0"
version = "24.8.0"
requires_python = ">=3.8"
summary = "A library for working with the color formats defined by HTML and CSS."
groups = ["default"]
files = [
{file = "webcolors-24.6.0-py3-none-any.whl", hash = "sha256:8cf5bc7e28defd1d48b9e83d5fc30741328305a8195c29a8e668fa45586568a1"},
{file = "webcolors-24.6.0.tar.gz", hash = "sha256:1d160d1de46b3e81e58d0a280d0c78b467dc80f47294b91b1ad8029d2cedb55b"},
{file = "webcolors-24.8.0-py3-none-any.whl", hash = "sha256:fc4c3b59358ada164552084a8ebee637c221e4059267d0f8325b3b560f6c7f0a"},
{file = "webcolors-24.8.0.tar.gz", hash = "sha256:08b07af286a01bcd30d583a7acadf629583d1f79bfef27dd2c2c5c263817277d"},
]
[[package]]
@ -2187,11 +2187,11 @@ files = [
[[package]]
name = "zipp"
version = "3.19.2"
version = "3.20.0"
requires_python = ">=3.8"
summary = "Backport of pathlib-compatible object wrapper for zip files"
groups = ["dev", "plot", "tests"]
files = [
{file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"},
{file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"},
{file = "zipp-3.20.0-py3-none-any.whl", hash = "sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d"},
{file = "zipp-3.20.0.tar.gz", hash = "sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31"},
]

View file

@ -20,7 +20,7 @@ dependencies = [
"pydantic-settings>=2.0.3",
"tqdm>=4.66.1",
'typing-extensions>=4.12.2;python_version<"3.11"',
"numpydantic>=1.3.0",
"numpydantic>=1.3.1",
"black>=24.4.2",
"pandas>=2.2.2",
]

View file

@ -153,8 +153,8 @@ class Adapter(BaseModel):
# SchemaAdapters that should be located under the same
# NamespacesAdapter when it's important to query across SchemaAdapters,
# so skip to avoid combinatoric walking
# if key == "imports" and type(input).__name__ == "SchemaAdapter":
# continue
if key == "imports" and type(input).__name__ == "SchemaAdapter":
continue
val = getattr(input, key)
yield (key, val)
if isinstance(val, (BaseModel, dict, list)):

View file

@ -26,7 +26,13 @@ from linkml_runtime.utils.compile_python import file_text
from linkml_runtime.utils.formatutils import remove_empty_items
from linkml_runtime.utils.schemaview import SchemaView
from nwb_linkml.includes.hdmf import DYNAMIC_TABLE_IMPORTS, DYNAMIC_TABLE_INJECTS
from nwb_linkml.includes.base import BASEMODEL_GETITEM
from nwb_linkml.includes.hdmf import (
DYNAMIC_TABLE_IMPORTS,
DYNAMIC_TABLE_INJECTS,
TSRVD_IMPORTS,
TSRVD_INJECTS,
)
from nwb_linkml.includes.types import ModelTypeString, NamedImports, NamedString, _get_name
OPTIONAL_PATTERN = re.compile(r"Optional\[([\w\.]*)\]")
@ -44,6 +50,7 @@ class NWBPydanticGenerator(PydanticGenerator):
' is stored in an NWB file")'
),
'object_id: Optional[str] = Field(None, description="Unique UUID for each object")',
BASEMODEL_GETITEM,
)
split: bool = True
imports: list[Import] = field(default_factory=lambda: [Import(module="numpy", alias="np")])
@ -232,7 +239,7 @@ class AfterGenerateClass:
Returns:
"""
if cls.cls.name == "DynamicTable":
if cls.cls.name in "DynamicTable":
cls.cls.bases = ["DynamicTableMixin"]
if cls.injected_classes is None:
@ -254,6 +261,21 @@ class AfterGenerateClass:
cls.cls.bases = ["DynamicTableRegionMixin", "VectorData"]
elif cls.cls.name == "AlignedDynamicTable":
cls.cls.bases = ["AlignedDynamicTableMixin", "DynamicTable"]
elif cls.cls.name == "TimeSeriesReferenceVectorData":
# in core.nwb.base, so need to inject and import again
cls.cls.bases = ["TimeSeriesReferenceVectorDataMixin", "VectorData"]
if cls.injected_classes is None:
cls.injected_classes = TSRVD_INJECTS.copy()
else:
cls.injected_classes.extend(TSRVD_INJECTS.copy())
if isinstance(cls.imports, Imports):
cls.imports += TSRVD_IMPORTS
elif isinstance(cls.imports, list):
cls.imports = Imports(imports=cls.imports) + TSRVD_IMPORTS
else:
cls.imports = TSRVD_IMPORTS.model_copy()
return cls

View file

@ -0,0 +1,14 @@
"""
Modifications to the ConfiguredBaseModel used by all generated classes
"""
BASEMODEL_GETITEM = """
def __getitem__(self, val: Union[int, slice]) -> Any:
\"\"\"Try and get a value from value or "data" if we have it\"\"\"
if hasattr(self, "value") and self.value is not None:
return self.value[val]
elif hasattr(self, "data") and self.data is not None:
return self.data[val]
else:
raise KeyError("No value or data field to index from")
"""

View file

@ -535,6 +535,109 @@ class AlignedDynamicTableMixin(DynamicTableMixin):
df.set_index((self.name, "id"), drop=True, inplace=True)
return df
@model_validator(mode="before")
@classmethod
def create_categories(cls, model: Dict[str, Any]) -> Dict:
"""
Construct categories from arguments.
the model dict is ordered after python3.6, so we can use that minus
anything in :attr:`.NON_COLUMN_FIELDS` to determine order implied from passage order
"""
if "categories" not in model:
categories = [
k for k in model if k not in cls.NON_CATEGORY_FIELDS and not k.endswith("_index")
]
model["categories"] = categories
else:
# add any columns not explicitly given an order at the end
categories = [
k
for k in model
if k not in cls.NON_COLUMN_FIELDS
and not k.endswith("_index")
and k not in model["categories"]
]
model["categories"].extend(categories)
return model
class TimeSeriesReferenceVectorDataMixin(VectorDataMixin):
"""
Mixin class for TimeSeriesReferenceVectorData -
very simple, just indexing the given timeseries object.
These shouldn't have additional fields in them, just the three columns
for index, span, and timeseries
"""
idx_start: NDArray[Any, int]
count: NDArray[Any, int]
timeseries: NDArray[Any, BaseModel]
@model_validator(mode="after")
def ensure_equal_length(self) -> "TimeSeriesReferenceVectorDataMixin":
assert len(self.idx_start) == len(self.timeseries) == len(self.count), (
f"Columns have differing lengths: idx: {len(self.idx_start)}, count: {len(self.count)},"
f" timeseries: {len(self.timeseries)}"
)
return self
def __len__(self) -> int:
"""Since we have ensured equal length, just return idx_start"""
return len(self.idx_start)
@overload
def _slice_helper(self, item: int) -> slice: ...
@overload
def _slice_helper(self, item: slice) -> List[slice]: ...
def _slice_helper(self, item: Union[int, slice]) -> Union[slice, List[slice]]:
if isinstance(item, (int, np.integer)):
return slice(self.idx_start[item], self.idx_start[item] + self.count[item])
else:
starts = self.idx_start[item]
ends = starts + self.count[item]
return [slice(start, end) for start, end in zip(starts, ends)]
def __getitem__(self, item: Union[int, slice, Iterable]) -> Any:
if self._index is not None:
raise NotImplementedError(
"VectorIndexing with TimeSeriesReferenceVectorData is not supported because it is"
" never done in the core schema."
)
if isinstance(item, (int, np.integer)):
return self.timeseries[self._slice_helper(item)]
elif isinstance(item, slice):
return [self.timeseries[subitem] for subitem in self._slice_helper(item)]
elif isinstance(item, Iterable):
return [self.timeseries[self._slice_helper(subitem)] for subitem in item]
else:
raise ValueError(
f"Dont know how to index with {item}, must be an int, slice, or iterable"
)
def __setitem__(self, key: Union[int, slice, Iterable], value: Any) -> None:
if self._index is not None:
raise NotImplementedError(
"VectorIndexing with TimeSeriesReferenceVectorData is not supported because it is"
" never done in the core schema."
)
if isinstance(key, (int, np.integer)):
self.timeseries[self._slice_helper(key)] = value
elif isinstance(key, slice):
for subitem in self._slice_helper(key):
self.timeseries[subitem] = value
elif isinstance(key, Iterable):
for subitem in key:
self.timeseries[self._slice_helper(subitem)] = value
else:
raise ValueError(
f"Dont know how to index with {key}, must be an int, slice, or iterable"
)
DYNAMIC_TABLE_IMPORTS = Imports(
imports=[
@ -577,3 +680,19 @@ DYNAMIC_TABLE_INJECTS = [
DynamicTableMixin,
AlignedDynamicTableMixin,
]
TSRVD_IMPORTS = Imports(
imports=[
Import(
module="typing",
objects=[
ObjectImport(name="overload"),
ObjectImport(name="Iterable"),
ObjectImport(name="Tuple"),
],
),
Import(module="pydantic", objects=[ObjectImport(name="model_validator")]),
]
)
"""Imports for TimeSeriesReferenceVectorData"""
TSRVD_INJECTS = [VectorDataMixin, TimeSeriesReferenceVectorDataMixin]

View file

@ -42,6 +42,7 @@ def test_walk_fields(nwb_core_fixture):
dtype = list(nwb_core_fixture.walk_fields(nwb_core_fixture, "dtype"))
dtype_havers = list(nwb_core_fixture.walk_types(nwb_core_fixture, (Dataset, Attribute)))
dtype_havers = [haver for haver in dtype_havers if haver.dtype is not None]
compound_dtypes = [len(d.dtype) for d in dtype_havers if isinstance(d.dtype, list)]
expected_dtypes = np.sum(compound_dtypes) + len(dtype_havers)
assert expected_dtypes == len(dtype)

View file

@ -15,6 +15,11 @@ from nwb_linkml.models import (
IntracellularResponsesTable,
IntracellularStimuliTable,
IntracellularRecordingsTable,
VoltageClampSeries,
VoltageClampSeriesData,
VoltageClampStimulusSeries,
VoltageClampStimulusSeriesData,
TimeSeriesReferenceVectorData,
)
@ -102,19 +107,68 @@ def units(request) -> Tuple[Units, list[np.ndarray], np.ndarray]:
return units, spike_times, spike_idx
def _icephys_stimulus_and_response(
i: int, electrode: IntracellularElectrode
) -> tuple[VoltageClampStimulusSeries, VoltageClampSeries]:
generator = np.random.default_rng()
n_samples = generator.integers(20, 50)
stimulus = VoltageClampStimulusSeries(
name=f"vcss_{i}",
data=VoltageClampStimulusSeriesData(value=[i] * n_samples),
stimulus_description=f"{i}",
sweep_number=i,
electrode=electrode,
)
response = VoltageClampSeries(
name=f"vcs_{i}",
data=VoltageClampSeriesData(value=[i] * n_samples),
stimulus_description=f"{i}",
electrode=electrode,
)
return stimulus, response
@pytest.fixture()
def intracellular_recordings_table() -> IntracellularRecordingsTable:
n_recordings = 10
generator = np.random.default_rng()
device = Device(name="my device")
electrode = IntracellularElectrode(
name="my_electrode", description="an electrode", device=device
)
stims = []
responses = []
for i in range(n_recordings):
stim, response = _icephys_stimulus_and_response(i, electrode)
stims.append(stim)
responses.append(response)
electrodes = IntracellularElectrodesTable(
name="intracellular_electrodes", electrode=[electrode] * n_recordings
)
stimuli = IntracellularStimuliTable(
name="intracellular_stimuli",
stimulus=TimeSeriesReferenceVectorData(
name="stimulus",
description="this should be optional",
idx_start=np.arange(n_recordings),
count=generator.integers(1, 10, (n_recordings,)),
timeseries=stims,
),
)
responses = IntracellularResponsesTable()
recordings_table = IntracellularRecordingsTable()
responses = IntracellularResponsesTable(
name="intracellular_responses",
response=TimeSeriesReferenceVectorData(
name="response",
description="this should be optional",
idx_start=np.arange(n_recordings),
count=generator.integers(1, 10, (n_recordings,)),
timeseries=responses,
),
)
recordings_table = IntracellularRecordingsTable(
electrodes=electrodes, stimuli=stimuli, responses=responses
)
return recordings_table

View file

@ -1,4 +1,5 @@
import numpy as np
import pandas as pd
# FIXME: Make this just be the output of the provider by patching into import machinery
from nwb_linkml.models.pydantic.core.v2_7_0.namespace import (
@ -6,6 +7,7 @@ from nwb_linkml.models.pydantic.core.v2_7_0.namespace import (
DynamicTableRegion,
ElectrodeGroup,
VectorIndex,
VoltageClampStimulusSeries,
)
from .conftest import _ragged_array
@ -159,3 +161,39 @@ def test_dynamictable_extra_coercion():
Extra fields should be coerced to VectorData and have their
indexing relationships handled when passed as plain arrays.
"""
def test_aligned_dynamictable(intracellular_recordings_table):
"""
Multiple aligned dynamictables should be indexable with a multiindex
"""
# can get a single row.. (check correctness below)
row = intracellular_recordings_table[0]
# can get a single table with its name
stimuli = intracellular_recordings_table["stimuli"]
assert stimuli.shape == (10, 1)
# nab a few rows to make the dataframe
rows = intracellular_recordings_table[0:3]
assert all(
rows.columns
== pd.MultiIndex.from_tuples(
[
("electrodes", "index"),
("electrodes", "electrode"),
("stimuli", "index"),
("stimuli", "stimulus"),
("responses", "index"),
("responses", "response"),
]
)
)
# ensure that we get the actual values from the TimeSeriesReferenceVectorData
# also tested separately
# each individual cell should be an array of VoltageClampStimulusSeries...
# and then we should be able to index within that as well
stims = rows["stimuli", "stimulus"][0]
for i in range(len(stims)):
assert isinstance(stims[i], VoltageClampStimulusSeries)
assert all([i == val for val in stims[i][:]])