Skip to content

Commit

Permalink
chore: add logic to import hooks by default if in third party provider
Browse files Browse the repository at this point in the history
  • Loading branch information
robcxyz committed Jan 8, 2024
1 parent a2ce1c7 commit 8117d31
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 65 deletions.
28 changes: 17 additions & 11 deletions tackle/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from tackle.settings import settings
from tackle.utils.files import read_config_file
from tackle.utils.paths import (
find_hooks_dir_from_tests,
find_hooks_directory_in_dir,
find_tackle_base_in_parent_dir,
find_tackle_base_in_parent_dir_with_exception,
Expand Down Expand Up @@ -143,7 +144,7 @@ def new_data(
# Data can be passed from one tackle call to another
if _data is None:
if context.data is None:
data = Data()
data = Data() # New data
else:
# This is a hack where we can take dict / list args in the main tackle
# function and to bypass the source logic, we simply create the data object
Expand Down Expand Up @@ -293,12 +294,12 @@ def update_source(
) from None
source.file = file_path
elif source.file is None:
file = find_tackle_file_in_dir(dir=source.directory)
file = find_tackle_file_in_dir(directory=source.directory)
if file is None:
file = find_tackle_file_in_dir(dir=source.base_dir)
file = find_tackle_file_in_dir(directory=source.base_dir)
source.file = file

source.hooks_dir = find_hooks_directory_in_dir(dir=source.base_dir)
source.hooks_dir = find_hooks_directory_in_dir(directory=source.base_dir)


def new_source_from_unknown_args(
Expand All @@ -314,7 +315,7 @@ def new_source_from_unknown_args(
there is, use that as source and don't consume the arg. Will raise error later if
the arg was not used.
"""
source.base_dir = find_tackle_base_in_parent_dir(dir=os.path.abspath('.'))
source.base_dir = find_tackle_base_in_parent_dir(directory=os.path.abspath('.'))
if source.base_dir is None:
raise exceptions.UnknownSourceException(
f"No tackle source or base directory was found with the "
Expand Down Expand Up @@ -367,7 +368,7 @@ def new_source(
source.find_in_parent = True
source.base_dir = find_tackle_base_in_parent_dir_with_exception(
context=context,
dir=os.path.abspath('..'),
directory=os.path.abspath('..'),
)
source.name = os.path.basename(source.base_dir)
update_source(
Expand All @@ -376,7 +377,7 @@ def new_source(
directory=directory,
file=file,
)
source.hooks_dir = find_hooks_directory_in_dir(dir=source.base_dir)
source.hooks_dir = find_hooks_directory_in_dir(directory=source.base_dir)
context.input.args.insert(0, first_arg)
elif isinstance(first_arg, (dict, list)):
# Will be picked up later in data as the input_raw to be parsed
Expand Down Expand Up @@ -408,7 +409,7 @@ def new_source(
directory=directory,
file=file,
)
source.hooks_dir = find_hooks_directory_in_dir(dir=source.base_dir)
source.hooks_dir = find_hooks_directory_in_dir(directory=source.base_dir)
# Repo
elif is_repo_url(first_arg):
if latest:
Expand All @@ -429,7 +430,7 @@ def new_source(
directory=directory,
file=file,
)
source.hooks_dir = find_hooks_directory_in_dir(dir=source.base_dir)
source.hooks_dir = find_hooks_directory_in_dir(directory=source.base_dir)
# Directory
elif is_directory_with_tackle(first_arg):
# Special case where the input is a path to a directory with a provider
Expand All @@ -450,7 +451,9 @@ def new_source(
source.file = file_path
source.directory = source.base_dir = str(Path(file_path).parent.absolute())
source.name = format_path_to_name(path=source.base_dir)
source.hooks_dir = find_hooks_directory_in_dir(dir=str(source.base_dir))
source.hooks_dir = find_hooks_directory_in_dir(
directory=str(source.base_dir)
)
else:
if _strict_source:
raise exceptions.UnknownSourceException(
Expand All @@ -471,7 +474,7 @@ def new_source(
# or parent directories.
source.base_dir = find_tackle_base_in_parent_dir_with_exception(
context=context,
dir=os.path.abspath('.'),
directory=os.path.abspath('.'),
)
source.name = format_path_to_name(path=source.base_dir)
update_source(
Expand All @@ -481,6 +484,9 @@ def new_source(
file=file,
)

if source.hooks_dir is None and source.base_dir is not None:
source.hooks_dir = find_hooks_dir_from_tests(source.base_dir)

return source


Expand Down
94 changes: 69 additions & 25 deletions tackle/utils/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,52 +168,46 @@ def is_repo_url(value) -> bool:
}


def find_hooks_directory_in_dir(dir: str) -> str:
for i in os.scandir(dir):
def find_hooks_directory_in_dir(directory: str) -> str:
for i in os.scandir(directory):
if i.is_dir() and i.name in DEFAULT_HOOKS_DIRECTORIES:
return os.path.abspath(os.path.join(dir, i))
return os.path.abspath(os.path.join(directory, i))


def find_tackle_file_in_dir(dir: str) -> str:
def find_tackle_file_in_dir(directory: str) -> str:
"""Return the path to a tackle file if it exists in a dir."""
for i in os.scandir(dir):
for i in os.scandir(directory):
if i.is_file() and i.name in DEFAULT_TACKLE_FILES:
return os.path.abspath(os.path.join(dir, i.name))
return os.path.abspath(os.path.join(directory, i.name))


def find_tackle_base_in_parent_dir(
dir: str,
fallback=None,
directory: str,
) -> Optional[str]:
"""
Recursively search in parent directories for a tackle base which is defined as
a directory with either a tackle file or a hooks directory.
"""
hooks_directory = find_hooks_directory_in_dir(dir=dir)
hooks_directory = find_hooks_directory_in_dir(directory=directory)
if hooks_directory is not None:
return dir
tackle_file = find_tackle_file_in_dir(dir=dir)
return directory
tackle_file = find_tackle_file_in_dir(directory=directory)
if tackle_file is not None:
return dir
return directory

if os.path.abspath(dir) == '/':
if fallback:
return fallback
else:
return None
if os.path.abspath(directory) == '/':
return None
return find_tackle_base_in_parent_dir(
dir=os.path.dirname(os.path.abspath(dir)),
fallback=fallback,
directory=os.path.dirname(os.path.abspath(directory)),
)


def find_tackle_base_in_parent_dir_with_exception(
context: 'Context',
dir: str,
fallback=None,
directory: str,
) -> str:
"""Call find_tackle_base_in_parent_dir and raise if no base is in parent."""
base = find_tackle_base_in_parent_dir(dir=dir, fallback=fallback)
base = find_tackle_base_in_parent_dir(directory=directory)
if base is None:
targets = list(DEFAULT_TACKLE_FILES) + list(DEFAULT_HOOKS_DIRECTORIES)
raise exceptions.UnknownSourceException(
Expand All @@ -225,11 +219,61 @@ def find_tackle_base_in_parent_dir_with_exception(
return base


def is_directory_with_tackle(dir: str) -> bool:
def find_tests_base_dir(directory: str) -> str | None:
"""Find the base dir of the parent test(s) directory."""
# Normalize the path
norm_dir = os.path.normpath(directory)

# Split the path into parts
path_split = norm_dir.split(os.sep)

# Handle the root slash or drive letter
if os.name == 'nt': # Windows
# Ensure the drive letter is preserved
if norm_dir.startswith("\\\\"): # UNC path
root = "\\\\" + path_split[0]
path_split = [root] + path_split[2:]
else:
path_split[0] += os.sep
else: # Unix-like
if norm_dir.startswith(os.sep):
path_split = [os.sep] + path_split[1:]

# Iterate in reverse to find 'test' or 'tests'
for i, v in enumerate(reversed(path_split)):
if v == 'test' or v == 'tests':
# Return the path index
return os.path.join(*path_split[: len(path_split) - 1 - i])


def find_hooks_dir_from_tests(directory: str) -> str | None:
"""
Super hacky way to determine if we are running tackle from a tests directory that is
not a native provider
"""
test_base = find_tests_base_dir(directory)
if test_base is None:
return
# Check if native provider
if os.path.isfile(os.path.join(test_base, '..', '.native')):
return
# Check if we are in tackle/tests/* dir
base_dir_contents = os.listdir(test_base)
if 'tackle' in base_dir_contents:
if os.path.isfile(os.path.join(test_base, 'tackle', 'parser.py')):
return
# Return hooks dir
if 'hooks' in base_dir_contents:
return os.path.join(test_base, 'hooks')
if '.hooks' in base_dir_contents:
return os.path.join(test_base, '.hooks')


def is_directory_with_tackle(directory: str) -> bool:
"""Return true if directory."""
if not os.path.isdir(dir):
if not os.path.isdir(directory):
return False
for i in os.scandir(dir):
for i in os.scandir(directory):
if i.is_dir() and i.name in DEFAULT_HOOKS_DIRECTORIES:
return True
if i.is_file() and i.name in DEFAULT_TACKLE_FILES:
Expand Down
10 changes: 10 additions & 0 deletions tests/factory/source/test_factory_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def test_factory_source_new_source_parameterized(args):
assert source.file.endswith('tackle.yaml')
# Check that the full path is in the source.file
assert 'factory' in source.file
assert source.hooks_dir is None


def test_factory_source_new_source_find_in_parent_arg(cd):
Expand All @@ -50,6 +51,7 @@ def test_factory_source_new_source_find_in_parent_arg(cd):
assert 'tests' in source.file
# Args should be reinserted
assert context.input.args[0] == {'foo': 'bar'}
assert source.hooks_dir is None


def test_factory_source_new_source_dict_arg():
Expand All @@ -63,6 +65,7 @@ def test_factory_source_new_source_dict_arg():
)
)
assert source.raw == {'foo': 'bar'}
assert source.hooks_dir is None


def test_factory_source_new_source_int_arg():
Expand All @@ -80,6 +83,7 @@ def test_factory_source_new_source_int_arg():
)
# Source is the tackle file in the current directory
assert source.base_dir.endswith('source')
assert source.hooks_dir is None


def test_factory_source_new_source_zip(cd):
Expand All @@ -100,6 +104,7 @@ def test_factory_source_new_source_zip(cd):
assert source.file.endswith('tackle.yaml')
assert 'zipped-provider' in source.file
assert not source.find_in_parent
assert source.hooks_dir is None


@pytest.fixture()
Expand Down Expand Up @@ -133,6 +138,7 @@ def test_factory_source_new_source_repo(mocker, repo_path):
assert not source.find_in_parent
assert source.hooks_dir is None
assert mock.called
assert source.hooks_dir is None


def test_factory_source_new_source_with_directory():
Expand All @@ -144,6 +150,7 @@ def test_factory_source_new_source_with_directory():
assert source.directory.endswith('a-dir')
assert source.file.endswith('tackle.yaml')
assert 'tests' in source.file
assert source.hooks_dir is None


def test_factory_source_new_source_with_file():
Expand All @@ -155,6 +162,7 @@ def test_factory_source_new_source_with_file():
assert source.directory == source.base_dir
assert source.file.endswith('a-file.yaml')
assert 'tests' in source.file
assert source.hooks_dir is None


def test_factory_source_new_source_with_file_arg():
Expand All @@ -167,6 +175,7 @@ def test_factory_source_new_source_with_file_arg():
assert source.directory == source.base_dir
assert source.file.endswith('a-file.yaml')
assert 'tests' in source.file
assert source.hooks_dir is None


def test_factory_source_as_dict():
Expand All @@ -177,6 +186,7 @@ def test_factory_source_as_dict():

# Should be without any data in it but still initialized
assert source.file is None
assert source.hooks_dir is None


EXCEPTION_FIXTURES = [
Expand Down
1 change: 1 addition & 0 deletions tests/utils/fixtures/a-provider/.hooks/foo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
foo()<-: bar
Empty file.
Loading

0 comments on commit 8117d31

Please sign in to comment.