marrow/package/loader.py
def traverse(obj, target:str, default=nodefault, executable:bool=False, separator:str='.', protect:bool=True):
	"""Traverse down an object, using getattr or getitem.
	
	If ``executable`` is ``True`` any executable function encountered will be, with no arguments. Traversal will
	continue on the result of that call.  You can change the separator as desired, i.e. to a '/'.
	
	By default attributes (but not array elements) prefixed with an underscore are taboo.  They will not resolve,
	raising a LookupError.
	
	Certain allowances are made: if a 'path segment' is numerical, it's treated as an array index. If attribute
	lookup fails, it will re-try on that object using array notation and continue from there.  This makes lookup
	very flexible.
	"""
	
	# TODO: Support numerical slicing, i.e. ``1:4``, or even just ``:-1`` and things.
	assert check_argument_types()
	
	value = obj
	remainder = target
	
	if not target:
		return obj
	
	while separator:
		name, separator, remainder = remainder.partition(separator)
		numeric = name.lstrip('-').isdigit()
		
		try:
			if numeric or (protect and name.startswith('_')):
				raise AttributeError()
			
			value = getattr(value, name)
			
			if executable and callable(value):
				value = value()
		
		except AttributeError:
			try:
				value = value[int(name) if numeric else name]
			
			except (KeyError, TypeError):
				if default is nodefault:
					raise LookupError("Could not resolve '" + target + "' on: " + repr(obj))
				
				return default
		
	return value
def traverse(obj, target:str, default=nodefault, executable:bool=False, separator:str='.', protect:bool=True):
	"""Traverse down an object, using getattr or getitem.
	
	If ``executable`` is ``True`` any executable function encountered will be, with no arguments. Traversal will
	continue on the result of that call.  You can change the separator as desired, i.e. to a '/'.
	
	By default attributes (but not array elements) prefixed with an underscore are taboo.  They will not resolve,
	raising a LookupError.
	
	Certain allowances are made: if a 'path segment' is numerical, it's treated as an array index. If attribute
	lookup fails, it will re-try on that object using array notation and continue from there.  This makes lookup
	very flexible.
	"""
	
	# TODO: Support numerical slicing, i.e. ``1:4``, or even just ``:-1`` and things.
	assert check_argument_types()
	
	value = obj
	remainder = target
	
	if not target:
		return obj
	
	while separator:
		name, separator, remainder = remainder.partition(separator)
		numeric = name.lstrip('-').isdigit()
		
		try:
			if numeric or (protect and name.startswith('_')):
				raise AttributeError()
			
			value = getattr(value, name)
			
			if executable and callable(value):
				value = value()
		
		except AttributeError:
			try:
				value = value[int(name) if numeric else name]
			
			except (KeyError, TypeError):
				if default is nodefault:
					raise LookupError("Could not resolve '" + target + "' on: " + repr(obj))
				
				return default
		
	return value
Traverse down an object, using getattr or getitem.
	
	If ``executable`` is ``True`` any executable function encountered will be, with no arguments. Traversal will
	continue on the result of that call.  You can change the separator as desired, i.e. to a '/'.
	
	By default attributes (but not array elements) prefixed with an underscore are taboo.  They will not resolve,
	raising a LookupError.
	
	Certain allowances are made: if a 'path segment' is numerical, it's treated as an array index. If attribute
	lookup fails, it will re-try on that object using array notation and continue from there.  This makes lookup
	very flexible.