Source code for python_utils.formatters

# pyright: reportUnnecessaryIsInstance=false
import datetime

from python_utils import types


[docs] def camel_to_underscore(name: str) -> str: '''Convert camel case style naming to underscore/snake case style naming If there are existing underscores they will be collapsed with the to-be-added underscores. Multiple consecutive capital letters will not be split except for the last one. >>> camel_to_underscore('SpamEggsAndBacon') 'spam_eggs_and_bacon' >>> camel_to_underscore('Spam_and_bacon') 'spam_and_bacon' >>> camel_to_underscore('Spam_And_Bacon') 'spam_and_bacon' >>> camel_to_underscore('__SpamAndBacon__') '__spam_and_bacon__' >>> camel_to_underscore('__SpamANDBacon__') '__spam_and_bacon__' ''' output: types.List[str] = [] for i, c in enumerate(name): if i > 0: pc = name[i - 1] if c.isupper() and not pc.isupper() and pc != '_': # Uppercase and the previous character isn't upper/underscore? # Add the underscore output.append('_') elif i > 3 and not c.isupper(): # Will return the last 3 letters to check if we are changing # case previous = name[i - 3 : i] if previous.isalpha() and previous.isupper(): output.insert(len(output) - 1, '_') output.append(c.lower()) return ''.join(output)
[docs] def apply_recursive( function: types.Callable[[str], str], data: types.OptionalScope = None, **kwargs: types.Any, ) -> types.OptionalScope: ''' Apply a function to all keys in a scope recursively >>> apply_recursive(camel_to_underscore, {'SpamEggsAndBacon': 'spam'}) {'spam_eggs_and_bacon': 'spam'} >>> apply_recursive(camel_to_underscore, {'SpamEggsAndBacon': { ... 'SpamEggsAndBacon': 'spam', ... }}) {'spam_eggs_and_bacon': {'spam_eggs_and_bacon': 'spam'}} >>> a = {'a_b_c': 123, 'def': {'DeF': 456}} >>> b = apply_recursive(camel_to_underscore, a) >>> b {'a_b_c': 123, 'def': {'de_f': 456}} >>> apply_recursive(camel_to_underscore, None) ''' if data is None: return None elif isinstance(data, dict): return { function(key): apply_recursive(function, value, **kwargs) for key, value in data.items() } else: return data
[docs] def timesince( dt: types.Union[datetime.datetime, datetime.timedelta], default: str = 'just now', ) -> str: ''' Returns string representing 'time since' e.g. 3 days ago, 5 hours ago etc. >>> now = datetime.datetime.now() >>> timesince(now) 'just now' >>> timesince(now - datetime.timedelta(seconds=1)) '1 second ago' >>> timesince(now - datetime.timedelta(seconds=2)) '2 seconds ago' >>> timesince(now - datetime.timedelta(seconds=60)) '1 minute ago' >>> timesince(now - datetime.timedelta(seconds=61)) '1 minute and 1 second ago' >>> timesince(now - datetime.timedelta(seconds=62)) '1 minute and 2 seconds ago' >>> timesince(now - datetime.timedelta(seconds=120)) '2 minutes ago' >>> timesince(now - datetime.timedelta(seconds=121)) '2 minutes and 1 second ago' >>> timesince(now - datetime.timedelta(seconds=122)) '2 minutes and 2 seconds ago' >>> timesince(now - datetime.timedelta(seconds=3599)) '59 minutes and 59 seconds ago' >>> timesince(now - datetime.timedelta(seconds=3600)) '1 hour ago' >>> timesince(now - datetime.timedelta(seconds=3601)) '1 hour and 1 second ago' >>> timesince(now - datetime.timedelta(seconds=3602)) '1 hour and 2 seconds ago' >>> timesince(now - datetime.timedelta(seconds=3660)) '1 hour and 1 minute ago' >>> timesince(now - datetime.timedelta(seconds=3661)) '1 hour and 1 minute ago' >>> timesince(now - datetime.timedelta(seconds=3720)) '1 hour and 2 minutes ago' >>> timesince(now - datetime.timedelta(seconds=3721)) '1 hour and 2 minutes ago' >>> timesince(datetime.timedelta(seconds=3721)) '1 hour and 2 minutes ago' ''' if isinstance(dt, datetime.timedelta): diff = dt else: now = datetime.datetime.now() diff = abs(now - dt) periods = ( (diff.days / 365, 'year', 'years'), (diff.days % 365 / 30, 'month', 'months'), (diff.days % 30 / 7, 'week', 'weeks'), (diff.days % 7, 'day', 'days'), (diff.seconds / 3600, 'hour', 'hours'), (diff.seconds % 3600 / 60, 'minute', 'minutes'), (diff.seconds % 60, 'second', 'seconds'), ) output: types.List[str] = [] for period, singular, plural in periods: if int(period): if int(period) == 1: output.append('%d %s' % (period, singular)) else: output.append('%d %s' % (period, plural)) if output: return f'{" and ".join(output[:2])} ago' return default