Skip to content

Commit

Permalink
str.lower -> str.casefold; support for plural acronyms (issue #80)
Browse files Browse the repository at this point in the history
  • Loading branch information
k1o0 committed Nov 13, 2024
1 parent e0e4cfc commit 55ce32a
Show file tree
Hide file tree
Showing 12 changed files with 27 additions and 23 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Also adds a new ALFPath class to replace alf path functions.
- ALF cache table generation has lower memory footprint
- setup in silent mode now uses defaults if base url matches default one
- bugfix: error downloading from http server with keep_uuids=True
- one.alf.spec.readableALF and one.alf.spec._dromedary preserve plural acronyms, e.g. 'ROIs'

### Added

Expand Down
9 changes: 5 additions & 4 deletions one/alf/spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,21 +246,22 @@ def _dromedary(string) -> str:
>>> _dromedary('motion_energy') == 'motionEnergy'
>>> _dromedary('passive_RFM') == 'passive RFM'
>>> _dromedary('FooBarBaz') == 'fooBarBaz'
>>> _dromedary('mpci ROIs') == 'mpciROIs'
See Also
--------
readableALF
"""
def _capitalize(x):
return x if x.isupper() else x.capitalize()
return x if re.match(r'^[A-Z]+s?$', x) else x.capitalize()
if not string: # short circuit on None and ''
return string
first, *other = re.split(r'[_\s]', string)
if len(other) == 0:
# Already camel/Pascal case, ensure first letter lower case
return first[0].lower() + first[1:]
# Convert to camel case, preserving all-uppercase elements
first = first if first.isupper() else first.casefold()
first = first if re.match(r'^[A-Z]+s?$', first) else first.lower()
return ''.join([first, *map(_capitalize, other)])


Expand Down Expand Up @@ -486,10 +487,10 @@ def readableALF(name: str, capitalize: bool = False) -> str:
"""
words = []
i = 0
matches = re.finditer(r'[A-Z](?=[a-z0-9])|(?<=[a-z0-9])[A-Z]', name)
matches = re.finditer(r'[A-Z](?=[a-rt-z0-9])|(?<=[a-z0-9])[A-Z]', name)
for j in map(re.Match.start, matches):
words.append(name[i:j])
i = j
words.append(name[i:])
display_str = ' '.join(map(lambda s: s if s.isupper() else s.lower(), words))
display_str = ' '.join(map(lambda s: s if re.match(r'^[A-Z]+s?$', s) else s.lower(), words))
return display_str[0].upper() + display_str[1:] if capitalize else display_str
6 changes: 3 additions & 3 deletions one/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1926,13 +1926,13 @@ def list_aggregates(self, relation: str, identifier: str = None,
r'^[\w\/]+(?=aggregates\/)', '', n=1, regex=True)
# The relation is the first part after 'aggregates', i.e. the second part
records['relation'] = records['rel_path'].map(
lambda x: x.split('aggregates')[-1].split('/')[1].lower())
records = records[records['relation'] == relation.lower()]
lambda x: x.split('aggregates')[-1].split('/')[1].casefold())
records = records[records['relation'] == relation.casefold()]

def path2id(p) -> str:
"""Extract identifier from relative path."""
parts = alfiles.rel_path_parts(p)[0].split('/')
idx = list(map(str.lower, parts)).index(relation.lower()) + 1
idx = list(map(str.casefold, parts)).index(relation.casefold()) + 1
return '/'.join(parts[idx:])

records['identifier'] = records['rel_path'].map(path2id)
Expand Down
8 changes: 4 additions & 4 deletions one/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def setup(client=None, silent=False, make_default=None, username=None, cache_dir
if par[k] and len(par[k]) >= 2 and par[k][0] in quotes and par[k][-1] in quotes:
warnings.warn('Do not use quotation marks with input answers', UserWarning)
ans = input('Strip quotation marks from response? [Y/n]:').strip() or 'y'
if ans.lower()[0] == 'y':
if ans.casefold()[0] == 'y':
par[k] = par[k].strip(quotes)
if k == 'ALYX_URL':
client = par[k]
Expand Down Expand Up @@ -185,17 +185,17 @@ def setup(client=None, silent=False, make_default=None, username=None, cache_dir
answer = input(
'Warning: the directory provided is already a cache for another URL. '
'This may cause conflicts. Would you like to change the cache location? [Y/n]')
if answer and answer[0].lower() == 'n':
if answer and answer[0].casefold() == 'n':
break
cache_dir = input(prompt) or cache_dir # Prompt for another directory

if make_default is None:
answer = input('Would you like to set this URL as the default one? [Y/n]')
make_default = (answer or 'y')[0].lower() == 'y'
make_default = (answer or 'y')[0].casefold() == 'y'

# Verify setup pars
answer = input('Are the above settings correct? [Y/n]')
if answer and answer.lower()[0] == 'n':
if answer and answer.casefold()[0] == 'n':
print('SETUP ABANDONED. Please re-run.')
return par_current
else:
Expand Down
2 changes: 1 addition & 1 deletion one/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def get_dataset_type(filename, dtypes):
if dt.name == obj_attr:
dataset_types.append(dt)
# Check whether pattern matches filename
elif fnmatch(filename.name.lower(), dt.filename_pattern.lower()):
elif fnmatch(filename.name.casefold(), dt.filename_pattern.casefold()):
dataset_types.append(dt)
n = len(dataset_types)
if n == 0:
Expand Down
4 changes: 2 additions & 2 deletions one/remote/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,9 +211,9 @@ def get_s3_from_alyx(alyx, repo_name=REPO_DEFAULT):
returned resource will use an unsigned signature.
"""
session_keys, bucket_name = get_aws_access_keys(alyx, repo_name)
no_creds = not any(filter(None, (v for k, v in session_keys.items() if 'key' in k.lower())))
no_creds = not any(filter(None, (v for k, v in session_keys.items() if 'key' in k.casefold())))
session = boto3.Session(**session_keys)
if no_creds and 'public' in bucket_name.lower():
if no_creds and 'public' in bucket_name.casefold():
config = Config(signature_version=UNSIGNED)
else:
config = None
Expand Down
2 changes: 1 addition & 1 deletion one/remote/globus.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def get_token(client_id, refresh_tokens=True):
fields = ('refresh_token', 'access_token', 'expires_at_seconds')
print('To get a new token, go to this URL and login: {0}'.format(authorize_url))
auth_code = input('Enter the code you get after login here (press "c" to cancel): ').strip()
if auth_code and auth_code.lower() != 'c':
if auth_code and auth_code.casefold() != 'c':
token_response = client.oauth2_exchange_code_for_tokens(auth_code)
globus_transfer_data = token_response.by_resource_server['transfer.api.globus.org']
return {k: globus_transfer_data.get(k) for k in fields}
Expand Down
2 changes: 2 additions & 0 deletions one/tests/alf/test_alf_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,13 +128,15 @@ def test_dromedary(self):
self.assertEqual(alf_spec._dromedary('passive_RFM'), 'passiveRFM')
self.assertEqual(alf_spec._dromedary('ROI Motion Energy'), 'ROIMotionEnergy')
self.assertEqual(alf_spec._dromedary(''), '')
self.assertEqual(alf_spec._dromedary('mpci ROIs'), 'mpciROIs')

def test_readable_ALF(self):
"""Test for one.alf.spec.readableALF function."""
self.assertEqual(alf_spec.readableALF('DAQData'), 'DAQ data')
self.assertEqual(alf_spec.readableALF('ROIMotion'), 'ROI motion')
self.assertEqual(alf_spec.readableALF('blueChipTime'), 'blue chip time')
self.assertEqual(alf_spec.readableALF('someROIDataset'), 'some ROI dataset')
self.assertEqual(alf_spec.readableALF('someROIsDataset'), 'some ROIs dataset')
self.assertEqual(alf_spec.readableALF('fooBAR'), 'foo BAR')
self.assertEqual(alf_spec.readableALF('fooBAR', capitalize=True), 'Foo BAR')

Expand Down
6 changes: 3 additions & 3 deletions one/tests/test_one.py
Original file line number Diff line number Diff line change
Expand Up @@ -1972,14 +1972,14 @@ def test_setup(self):
url = TEST_DB_1['base_url']

def mock_input(prompt):
if prompt.lower().startswith('warning'):
if prompt.casefold().startswith('warning'):
if not getattr(mock_input, 'conflict_warn', False): # Checks both responses
mock_input.conflict_warn = True
return 'y'
return 'n'
elif 'download cache' in prompt.lower():
elif 'download cache' in prompt.casefold():
return Path(self.tempdir.name).joinpath('downloads').as_posix()
elif 'url' in prompt.lower():
elif 'url' in prompt.casefold():
return url
else:
return 'mock_input'
Expand Down
4 changes: 2 additions & 2 deletions one/tests/test_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ def setUp(self) -> None:

def _mock_input(self, prompt, **kwargs):
"""Stub function for builtins.input"""
if prompt.lower().startswith('warning'):
if prompt.casefold().startswith('warning'):
return 'n'
elif 'url' in prompt.lower():
elif 'url' in prompt.casefold():
return self.url
else:
for k, v in kwargs.items():
Expand Down
4 changes: 2 additions & 2 deletions one/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,11 +472,11 @@ def autocomplete(term, search_terms) -> str:
"""
Validate search term and return complete name, e.g. autocomplete('subj') == 'subject'.
"""
term = term.lower()
term = term.casefold()
# Check if term already complete
if term in search_terms:
return term
full_key = (x for x in search_terms if x.lower().startswith(term))
full_key = (x for x in search_terms if x.casefold().startswith(term))
key_ = next(full_key, None)
if not key_:
raise ValueError(f'Invalid search term "{term}", see `one.search_terms()`')
Expand Down
2 changes: 1 addition & 1 deletion one/webclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def wrapper_decorator(alyx_client, *args, expires=None, clobber=False, **kwargs)
The REST response JSON either from cached file or directly from remote.
"""
expires = expires or alyx_client.default_expiry
mode = (alyx_client.cache_mode or '').lower()
mode = (alyx_client.cache_mode or '').casefold()
if args[0].__name__ != mode and mode != '*':
return method(alyx_client, *args, **kwargs)
# Check cache
Expand Down

0 comments on commit 55ce32a

Please sign in to comment.