ONE-api 3.1.1__py3-none-any.whl → 3.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
one/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  """The Open Neurophysiology Environment (ONE) API."""
2
- __version__ = '3.1.1'
2
+ __version__ = '3.2.1'
one/alf/path.py CHANGED
@@ -859,6 +859,36 @@ class PureALFPath(pathlib.PurePath): # py3.12 supports direct subclassing
859
859
  """tuple of str: the full ALF path parts, with empty strings for missing parts."""
860
860
  return tuple(p or '' for p in self.parse_alf_path(as_dict=False))
861
861
 
862
+ @property
863
+ def lab(self):
864
+ """str: The lab part of the ALF path, or an empty str if not present."""
865
+ return self.session_parts[0]
866
+
867
+ @property
868
+ def subject(self):
869
+ """str: The subject part of the ALF path, or an empty str if not present."""
870
+ return self.session_parts[1]
871
+
872
+ @property
873
+ def date(self):
874
+ """str: The date part of the ALF path, or an empty str if not present."""
875
+ return self.session_parts[2]
876
+
877
+ @property
878
+ def sequence(self):
879
+ """str: The number part of the ALF path, or an empty str if not present."""
880
+ return self.session_parts[3]
881
+
882
+ @property
883
+ def collection(self):
884
+ """str: The collection part of the ALF path, or an empty str if not present."""
885
+ return self.alf_parts[4]
886
+
887
+ @property
888
+ def revision(self):
889
+ """str: The revision part of the ALF path, or an empty str if not present."""
890
+ return self.alf_parts[5]
891
+
862
892
  @property
863
893
  def namespace(self):
864
894
  """str: The namespace part of the ALF name, or and empty str if not present."""
@@ -884,6 +914,134 @@ class PureALFPath(pathlib.PurePath): # py3.12 supports direct subclassing
884
914
  """str: The extra part of the ALF name, or and empty str if not present."""
885
915
  return self.dataset_name_parts[4]
886
916
 
917
+ def with_lab(self, lab, strict=False):
918
+ """Return a new path with the ALF lab changed.
919
+
920
+ Parameters
921
+ ----------
922
+ lab : str
923
+ An ALF lab name part to use.
924
+ strict : bool, optional
925
+ If True, the lab part must be present in the path, otherwise the lab/Subjects/ part
926
+ is added if not present.
927
+
928
+ Returns
929
+ -------
930
+ PureALFPath
931
+ The same file path but with the lab part replaced with the input.
932
+
933
+ Raises
934
+ ------
935
+ ValueError
936
+ The lab name is invalid.
937
+ ALFInvalid
938
+ The path is not a valid ALF session path, or the lab part is not present in the path
939
+ when strict is True.
940
+
941
+ """
942
+ if not (lab and spec.regex('^{lab}$').match(lab)):
943
+ raise ValueError(f'Invalid lab name: {lab}')
944
+ if not self.subject or (strict and not self.lab): # FIXME check logic
945
+ raise ALFInvalid(str(self))
946
+
947
+ pattern = spec.regex(SESSION_SPEC)
948
+ repl = fr'{lab}/Subjects/\g<subject>/\g<date>/\g<number>'
949
+ return self.__class__(pattern.sub(repl, self.as_posix(), count=1))
950
+
951
+ def with_subject(self, subject):
952
+ """Return a new path with the ALF subject changed.
953
+
954
+ Parameters
955
+ ----------
956
+ subject : str
957
+ An ALF subject name part to use.
958
+
959
+ Returns
960
+ -------
961
+ PureALFPath
962
+ The same file path but with the subject part replaced with the input.
963
+
964
+ Raises
965
+ ------
966
+ ValueError
967
+ The subject name is invalid.
968
+ ALFInvalid
969
+ The path is not a valid ALF session path.
970
+
971
+ """
972
+ if not (subject and spec.regex('^{subject}$').match(subject)):
973
+ raise ValueError(f'Invalid subject name: {subject}')
974
+ if not self.subject:
975
+ raise ALFInvalid(str(self))
976
+
977
+ pattern = spec.regex('{subject}/{date}/{number}')
978
+ repl = fr'{subject}/\g<date>/\g<number>'
979
+ return self.__class__(pattern.sub(repl, self.as_posix()), count=1)
980
+
981
+ def with_date(self, date):
982
+ """Return a new path with the ALF date changed.
983
+
984
+ Parameters
985
+ ----------
986
+ date : str, datetime.datetime, datetime.date
987
+ An ALF date part to use, in YYYY-MM-DD format.
988
+
989
+ Returns
990
+ -------
991
+ PureALFPath
992
+ The same file path but with the date part replaced with the input.
993
+
994
+ Raises
995
+ ------
996
+ ValueError
997
+ The date is not in YYYY-MM-DD format.
998
+ ALFInvalid
999
+ The path is not a valid ALF session path.
1000
+
1001
+ """
1002
+ if date and not isinstance(date, str):
1003
+ date = str(date)[:10]
1004
+ if not (date and spec.regex('^{date}$').match(date)):
1005
+ raise ValueError(f'Invalid date: {date}')
1006
+ if not self.date:
1007
+ raise ALFInvalid(str(self))
1008
+
1009
+ pattern = spec.regex('{subject}/{date}/{number}')
1010
+ repl = fr'\g<subject>/{date}/\g<number>'
1011
+ return self.__class__(pattern.sub(repl, self.as_posix()), count=1)
1012
+
1013
+ def with_sequence(self, number):
1014
+ """Return a new path with the ALF number changed.
1015
+
1016
+ Parameters
1017
+ ----------
1018
+ number : str, int
1019
+ An ALF number part to use, as a string or integer.
1020
+
1021
+ Returns
1022
+ -------
1023
+ PureALFPath
1024
+ The same file path but with the number part replaced with the input.
1025
+
1026
+ Raises
1027
+ ------
1028
+ ValueError
1029
+ The number is not a valid ALF number.
1030
+ ALFInvalid
1031
+ The path is not a valid ALF session path.
1032
+
1033
+ """
1034
+ if isinstance(number, str):
1035
+ number = int(number.strip())
1036
+ if number is None or not spec.regex('^{number}$').match(str(number)):
1037
+ raise ValueError(f'Invalid number: {number}')
1038
+ if not self.sequence:
1039
+ raise ALFInvalid(str(self))
1040
+
1041
+ pattern = spec.regex('{subject}/{date}/{number}')
1042
+ repl = fr'\g<subject>/\g<date>/{number:03d}'
1043
+ return self.__class__(pattern.sub(repl, self.as_posix()), count=1)
1044
+
887
1045
  def with_object(self, obj):
888
1046
  """Return a new path with the ALF object changed.
889
1047
 
@@ -1084,6 +1242,58 @@ class PureALFPath(pathlib.PurePath): # py3.12 supports direct subclassing
1084
1242
  """
1085
1243
  return padded_sequence(path)
1086
1244
 
1245
+ def with_collection(self, collection):
1246
+ """Return a new path with the ALF collection part added/changed.
1247
+
1248
+ NB: The ALFPath must include the session parts (subject/date/number) for this to work.
1249
+
1250
+ Parameters
1251
+ ----------
1252
+ collection : str
1253
+ An ALF collection part to use.
1254
+
1255
+ Returns
1256
+ -------
1257
+ PureALFPath
1258
+ The same file path but with the collection part added or replaced with the input.
1259
+
1260
+ Raises
1261
+ ------
1262
+ ValueError
1263
+ The collection name is invalid.
1264
+ The path does not include the session parts (subject/date/number).
1265
+ ALFInvalid
1266
+ The path is not a valid ALF session path.
1267
+
1268
+ """
1269
+ collection = pathlib.PurePath(collection or '').as_posix().strip('/')
1270
+ if not (collection and spec.regex('^{collection}$').match(collection)):
1271
+ raise ValueError(f'Invalid collection name: {collection}')
1272
+ # Check path contains session parts
1273
+ if not self.session_path():
1274
+ raise ValueError(
1275
+ 'Cannot add collection to a path without session parts, e.g. subject/date/number'
1276
+ )
1277
+ # If the path is a session path, simply append the collection to it
1278
+ if self.is_session_path():
1279
+ return self.joinpath(collection)
1280
+ # Otherwise substitute the collection with regex
1281
+ string = self.as_posix()
1282
+ if not self.is_dataset():
1283
+ cpat = spec.regex(f'^{COLLECTION_SPEC}$')
1284
+ if cpat.match(self.relative_to_session().as_posix() + '/'):
1285
+ string += '/' # ensure trailing slash for matching folder paths
1286
+ else:
1287
+ raise ALFInvalid(str(self))
1288
+ # Replace the collection part in the path
1289
+ # NB: We don't use SESSION_SPEC here to avoid handling optional lab part
1290
+ pattern = spec.regex('{subject}/{date}/{number}/' + COLLECTION_SPEC)
1291
+ match = pattern.search(string)
1292
+ repl = fr'\g<subject>/\g<date>/\g<number>/{collection}/'
1293
+ if match.groupdict()['revision']:
1294
+ repl += r'#\g<revision>#/'
1295
+ return self.__class__(pattern.sub(repl, string), count=1)
1296
+
1087
1297
  def with_revision(self, revision):
1088
1298
  """Return a new path with the ALF revision part added/changed.
1089
1299
 
one/api.py CHANGED
@@ -39,7 +39,7 @@ from one import util
39
39
 
40
40
  _logger = logging.getLogger(__name__)
41
41
  __all__ = ['ONE', 'One', 'OneAlyx']
42
- SAVE_ON_DELETE = (os.environ.get('ONE_SAVE_ON_DELETE') or '1').casefold() in ('true', '1')
42
+ SAVE_ON_DELETE = (os.environ.get('ONE_SAVE_ON_DELETE') or '0').casefold() in ('true', '1')
43
43
  """bool: Whether to save modified cache tables on delete."""
44
44
 
45
45
  _logger.debug('ONE_SAVE_ON_DELETE: %s', SAVE_ON_DELETE)
@@ -221,7 +221,7 @@ class One(ConversionMixin):
221
221
  created = created.isoformat(sep=' ', timespec='minutes')
222
222
  meta['raw'][table]['date_created'] = created
223
223
 
224
- with FileLock(save_dir, log=_logger, timeout=TIMEOUT, timeout_action='delete'):
224
+ with FileLock(save_dir / '.ONE', log=_logger, timeout=TIMEOUT, timeout_action='delete'):
225
225
  _logger.info('Saving cache tables...')
226
226
  for table in filter(lambda x: not x[0] == '_', caches.keys()):
227
227
  metadata = meta['raw'].get(table, {})
one/webclient.py CHANGED
@@ -77,7 +77,7 @@ class _JSONEncoder(json.JSONEncoder):
77
77
 
78
78
 
79
79
  def _cache_response(method):
80
- """Decorator for the generic request method for caching REST reponses.
80
+ """Decorator for the generic request method for caching REST responses.
81
81
 
82
82
  Caches the result of the query and on subsequent calls, returns cache instead of hitting the
83
83
  database.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ONE-api
3
- Version: 3.1.1
3
+ Version: 3.2.1
4
4
  Summary: Open Neurophysiology Environment
5
5
  Author: IBL Staff
6
6
  License: MIT
@@ -1,15 +1,15 @@
1
- one/__init__.py,sha256=w5ArMDExoOvziPE_Nix7Iafegwtcr75LLiks1Yl0Cl0,76
2
- one/api.py,sha256=YypYLcHsGEJ-gY7TEx5YR3NNT4AF5uIpZ0-H05ydFlQ,134054
1
+ one/__init__.py,sha256=hAxRddzvqKmr-xfJHfmZ-iggW7DALOJykIkPI2NJ2ko,76
2
+ one/api.py,sha256=XwMBFnkAqzmzwl1AOFLNZglT1xuw3IhLT7C8BHpET9A,134063
3
3
  one/converters.py,sha256=icKlwPyxf3tJtyOFBj_SG06QDLIZLdTGalCSk1-cAvk,30733
4
4
  one/params.py,sha256=zwR0Yq09ztROfH3fJsCUc-IDs_PBixT3WU3dm1vX728,15050
5
5
  one/registration.py,sha256=cWvQFAzCF04wMZJjdOzBPJkYOJ3BO2KEgqtVG7qOlmA,36177
6
6
  one/util.py,sha256=NUG_dTz3_4GXYG49qSql6mFCBkaVaq7_XdecRPRszJQ,20173
7
- one/webclient.py,sha256=s7O5S9DhGnxj6g2xMfq1NsyvoBXd_Zz9UEw74A9VnBE,50409
7
+ one/webclient.py,sha256=PUFo2ts9tl1HSRGsnDO5fVBNI8KRuGf9GJGSCcW50B8,50410
8
8
  one/alf/__init__.py,sha256=DaFIi7PKlurp5HnyNOpJuIlY3pyhiottFpJfxR59VsY,70
9
9
  one/alf/cache.py,sha256=09NDLGIUA6GcCJgzbDxSFpgq9sU6gyCuHIF8c7zHAMM,24814
10
10
  one/alf/exceptions.py,sha256=6Gw8_ZObLnuYUpY4i0UyU4IA0kBZMBnJBGQyzIcDlUM,3427
11
11
  one/alf/io.py,sha256=7mmd1wJwh7qok0WKkpGzkLxEuw4WSnaQ6EkGVvaODLI,30798
12
- one/alf/path.py,sha256=LpB0kdTPBzuEdAWGe5hFDWCKSq4FgCdiDk8LwVzv-T0,46793
12
+ one/alf/path.py,sha256=0O5OtVBD4dsaq8Y0iP6NhrAcdkEewR79y06BnwHpDtk,54006
13
13
  one/alf/spec.py,sha256=eWW4UDMsyYO52gzadZ-l09X2adVbXwpRKUBtd7NlYMw,20711
14
14
  one/remote/__init__.py,sha256=pasT1r9t2F2_-hbx-5EKlJ-yCM9ItucQbeDHUbFJcyA,40
15
15
  one/remote/aws.py,sha256=DurvObYa8rgyYQMWeaq1ol7PsdJoE9bVT1NnH2rxRRE,10241
@@ -30,8 +30,8 @@ one/tests/fixtures/rest_responses/6dc96f7e9bcc6ac2e7581489b9580a6cd3f28293,sha25
30
30
  one/tests/fixtures/rest_responses/db1731fb8df0208944ae85f76718430813a8bf50,sha256=Dki7cTDg1bhbtPkuiv9NPRsuhM7TuicxoLcGtRD4D8g,209
31
31
  one/tests/fixtures/rest_responses/dcce48259bb929661f60a02a48563f70aa6185b3,sha256=skaKl6sPgTyrufCIGNVNJfccXM-jSjYvAdyqNS3HXuA,416
32
32
  one/tests/fixtures/rest_responses/f530d6022f61cdc9e38cc66beb3cb71f3003c9a1,sha256=EOqhNIVcmZ7j7aF09g5StFY434f2xbxwZLHwfeM4tug,22530
33
- one_api-3.1.1.dist-info/licenses/LICENSE,sha256=W6iRQJcr-tslNfY4gL98IWvPtpe0E3tcWCFOD3IFUqg,1087
34
- one_api-3.1.1.dist-info/METADATA,sha256=DtcBaq4qRm80sgWhjnQKvIC2yle_bdSTope6U8iAv7M,4170
35
- one_api-3.1.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
36
- one_api-3.1.1.dist-info/top_level.txt,sha256=LIsI2lzmA5jh8Zrw5dzMdE3ydLgmq-WF6rpoxSVDSAY,4
37
- one_api-3.1.1.dist-info/RECORD,,
33
+ one_api-3.2.1.dist-info/licenses/LICENSE,sha256=W6iRQJcr-tslNfY4gL98IWvPtpe0E3tcWCFOD3IFUqg,1087
34
+ one_api-3.2.1.dist-info/METADATA,sha256=FRuJQkda6IZP4UUoL6IiOtCN63RZW0HmYOkEcB4T_Vk,4170
35
+ one_api-3.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
36
+ one_api-3.2.1.dist-info/top_level.txt,sha256=LIsI2lzmA5jh8Zrw5dzMdE3ydLgmq-WF6rpoxSVDSAY,4
37
+ one_api-3.2.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5