From 060c979a27fce7c8dd028fa0020dd8832ad2d747 Mon Sep 17 00:00:00 2001 From: Naggafin Date: Wed, 25 Dec 2024 19:07:03 -0500 Subject: [PATCH 1/6] bug fix --- django_fastdev/apps.py | 49 +++++++++++++++++++++++++----------------- tests/forms.py | 8 +++++++ tests/test_forms.py | 19 +++++++++++++--- 3 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 tests/forms.py diff --git a/django_fastdev/apps.py b/django_fastdev/apps.py index df5210b..814c935 100644 --- a/django_fastdev/apps.py +++ b/django_fastdev/apps.py @@ -83,6 +83,15 @@ def get_gitignore_path(): return None +def get_venv_path(): + return os.environ.get("VIRTUAL_ENV", None) + + +def get_venv_folder_name(): + venv_path = get_venv_path() + return os.path.basename(venv_path) if venv_path else venv_path + + def is_absolute_url(url): return bool(url.startswith('/') or url.startswith('http://') or url.startswith('https://')) @@ -232,7 +241,6 @@ def is_from_project(cls): Args: cls: The class to check. - project_root: The root directory of your project (absolute path). Returns: bool: True if the class originates from the project directory, False otherwise. @@ -243,11 +251,12 @@ def is_from_project(cls): if not module or not hasattr(module, "__file__"): return False - venv_dir = os.environ.get("VIRTUAL_ENV", "") + venv_dir = get_venv_path() + project_dir = get_path_for_django_project() module_path = os.path.abspath(module.__file__) - return module_path.startswith( - str(settings.BASE_DIR) - ) and not module_path.startswith(venv_dir) + return module_path.startswith(str(project_dir)) and not ( + bool(venv_dir) and module_path.startswith(venv_dir) + ) def fastdev_ignore(target): @@ -256,14 +265,6 @@ def fastdev_ignore(target): return target -def get_venv_folder_name(): - import os - - path_to_venv = os.environ["VIRTUAL_ENV"] - venv_folder = os.path.basename(path_to_venv) - return venv_folder - - @cache def get_ignored_template_list(): ignored_templates_settings = getattr(settings, 'FASTDEV_IGNORED_TEMPLATES', []) @@ -308,14 +309,15 @@ def resolve_override(self, context, ignore_failures=False, ignore_failures_for_r if not strict_template_checking(): # worry only about templates inside our project dir; if they # exist elsewhere, then go to standard django behavior - venv_dir = os.environ.get('VIRTUAL_ENV', '') + venv_dir = get_venv_path() + project_dir = get_path_for_django_project() origin = context.template.origin.name if ( - origin != '' and - 'django-fastdev/tests/' not in origin + origin != '' + and 'django-fastdev/tests/' not in origin and ( - not origin.startswith(str(settings.BASE_DIR)) - or (venv_dir and origin.startswith(venv_dir)) + not origin.startswith(str(project_dir)) + or (bool(venv_dir) and origin.startswith(venv_dir)) ) ): return orig_resolve(self, context, ignore_failures=ignore_failures) @@ -409,12 +411,19 @@ def if_render_override(self, context): def fastdev_full_clean(self): orig_form_full_clean(self) # check if class is from our project, or strict form checking is enabled - if is_from_project(type(self)) or strict_form_checking() and not getattr(self, 'fastdev_ignore', False): + if (is_from_project(type(self)) or strict_form_checking()) and not getattr( + self, 'fastdev_ignore', False + ): from django.conf import settings + if settings.DEBUG: prefix = 'clean_' for name in dir(self): - if name.startswith(prefix) and callable(getattr(self, name)) and name[len(prefix):] not in self.fields: + if ( + name.startswith(prefix) + and callable(getattr(self, name)) + and name[len(prefix) :] not in self.fields + ): fields = '\n '.join(sorted(self.fields.keys())) raise InvalidCleanMethod(f"""Clean method {name} of class {self.__class__.__name__} won't apply to any field. Available fields: diff --git a/tests/forms.py b/tests/forms.py new file mode 100644 index 0000000..724f766 --- /dev/null +++ b/tests/forms.py @@ -0,0 +1,8 @@ +from django import forms + + +class IgnoredForm(forms.Form): + my_field = forms.CharField() + + def clean_nonexistent_field(self): + pass diff --git a/tests/test_forms.py b/tests/test_forms.py index cb50b9d..bf44607 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -4,7 +4,7 @@ Form, ) -from django_fastdev.apps import InvalidCleanMethod +from django_fastdev.apps import InvalidCleanMethod, fastdev_ignore def test_ok_form_works(settings): @@ -30,7 +30,7 @@ def clean_flield(self): MyForm().errors settings.DEBUG = True - # set strict mode otherwise test will fail (because dynamically type form; doesn't exist in module) + # set strict mode otherwise test will fail (because dynamically typed form; doesn't have module.__file__ attribute) settings.FASTDEV_STRICT_FORM_CHECKING = True with pytest.raises(InvalidCleanMethod) as e: MyForm().errors @@ -57,9 +57,22 @@ def clean_flield(self): MyForm().errors settings.DEBUG = True - # set strict mode otherwise test will fail (because dynamically type form; doesn't exist in module) + # set strict mode otherwise test will fail (because dynamically typed form; doesn't have module.__file__ attribute) settings.FASTDEV_STRICT_FORM_CHECKING = True with pytest.raises(InvalidCleanMethod) as e: MyForm().errors assert str(e.value) == """Clean method clean_flield of class MyForm won't apply to any field. Available fields:\n\n field""" + + +def test_ignored_form_works(settings): + from .forms import IgnoredForm + + IgnoredForm().errors + + settings.DEBUG = True + with pytest.raises(InvalidCleanMethod) as e: + IgnoredForm().errors + + IgnoredForm = fastdev_ignore(IgnoredForm) + IgnoredForm().errors From 8b32ef51e024d30273d8161144aa1225c7a574a7 Mon Sep 17 00:00:00 2001 From: Naggafin Date: Thu, 26 Dec 2024 17:29:35 -0500 Subject: [PATCH 2/6] reformatted for readability --- django_fastdev/apps.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/django_fastdev/apps.py b/django_fastdev/apps.py index 814c935..31f7abc 100644 --- a/django_fastdev/apps.py +++ b/django_fastdev/apps.py @@ -254,9 +254,13 @@ def is_from_project(cls): venv_dir = get_venv_path() project_dir = get_path_for_django_project() module_path = os.path.abspath(module.__file__) - return module_path.startswith(str(project_dir)) and not ( - bool(venv_dir) and module_path.startswith(venv_dir) - ) + + if module_path.startswith(project_dir): + # check against venv after project dir matches, + # just in case venv resides in project dir + if venv_dir and module_path.startswith(venv_dir): + return False + return True def fastdev_ignore(target): From d342868161c77e8c0fa1b730e16e80813be8822b Mon Sep 17 00:00:00 2001 From: Naggafin Date: Thu, 26 Dec 2024 17:32:47 -0500 Subject: [PATCH 3/6] reformatted for readability --- django_fastdev/apps.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django_fastdev/apps.py b/django_fastdev/apps.py index 31f7abc..f619796 100644 --- a/django_fastdev/apps.py +++ b/django_fastdev/apps.py @@ -255,10 +255,10 @@ def is_from_project(cls): project_dir = get_path_for_django_project() module_path = os.path.abspath(module.__file__) - if module_path.startswith(project_dir): + if module_path.startswith(str(project_dir)): # check against venv after project dir matches, # just in case venv resides in project dir - if venv_dir and module_path.startswith(venv_dir): + if venv_dir and module_path.startswith(str(venv_dir)): return False return True @@ -321,7 +321,7 @@ def resolve_override(self, context, ignore_failures=False, ignore_failures_for_r and 'django-fastdev/tests/' not in origin and ( not origin.startswith(str(project_dir)) - or (bool(venv_dir) and origin.startswith(venv_dir)) + or (bool(venv_dir) and origin.startswith(str(venv_dir))) ) ): return orig_resolve(self, context, ignore_failures=ignore_failures) From 580b7e80ffe98c7621d04a58042f7ec84750ce7c Mon Sep 17 00:00:00 2001 From: Naggafin Date: Mon, 30 Dec 2024 19:26:48 -0500 Subject: [PATCH 4/6] fixed implicit return --- django_fastdev/apps.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/django_fastdev/apps.py b/django_fastdev/apps.py index f619796..509377d 100644 --- a/django_fastdev/apps.py +++ b/django_fastdev/apps.py @@ -247,20 +247,23 @@ def is_from_project(cls): """ module = getmodule(cls) - # check if built-in module or dynamically created class + # exit early if the module is built-in or dynamically created if not module or not hasattr(module, "__file__"): return False - venv_dir = get_venv_path() - project_dir = get_path_for_django_project() module_path = os.path.abspath(module.__file__) + project_dir = get_path_for_django_project() + venv_dir = get_venv_path() - if module_path.startswith(str(project_dir)): - # check against venv after project dir matches, - # just in case venv resides in project dir - if venv_dir and module_path.startswith(str(venv_dir)): - return False - return True + # check if the module belongs to the project directory + if not module_path.startswith(str(project_dir)): + return False + + # exclude modules from the virtual environment if applicable + if venv_dir and module_path.startswith(str(venv_dir)): + return False + + return True def fastdev_ignore(target): From 75280a6e526863d88ce38078ef081869e69c8bbb Mon Sep 17 00:00:00 2001 From: Naggafin Date: Mon, 30 Dec 2024 19:55:39 -0500 Subject: [PATCH 5/6] updated get_venv_path() for reliability per suggestion by @danlamanna --- django_fastdev/apps.py | 50 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/django_fastdev/apps.py b/django_fastdev/apps.py index 509377d..16cc19c 100644 --- a/django_fastdev/apps.py +++ b/django_fastdev/apps.py @@ -84,7 +84,55 @@ def get_gitignore_path(): def get_venv_path(): - return os.environ.get("VIRTUAL_ENV", None) + """ + Retrieve the path to the active virtual environment, if any. + + Returns: + str or None: The path to the virtual environment, or None if not in a virtual environment. + """ + # 1. check the VIRTUAL_ENV environment variable + venv_path = os.getenv("VIRTUAL_ENV") + if venv_path: + return os.path.abspath(venv_path) + + # 2. check for `sys.real_prefix` (used by `virtualenv`) + if hasattr(sys, "real_prefix") and sys.real_prefix != sys.prefix: + return sys.real_prefix + + # 3. compare `sys.base_prefix` with `sys.prefix` (used by `venv`) + if hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix: + return sys.prefix + + # 4. look for `pyvenv.cfg` in parent directories of `sys.executable` + venv_cfg_path = find_pyvenv_cfg(os.path.dirname(sys.executable)) + if venv_cfg_path: + return os.path.dirname(venv_cfg_path) + + # if all else fails, return None + return None + + +def find_pyvenv_cfg(start_path): + """ + Recursively search for `pyvenv.cfg` in the given directory and its parents. + + Args: + start_path (str): The directory to start searching from. + + Returns: + str or None: The path to `pyvenv.cfg` if found, or None otherwise. + """ + current_path = start_path + while True: + potential_cfg = os.path.join(current_path, "pyvenv.cfg") + if os.path.isfile(potential_cfg): + return potential_cfg + parent_path = os.path.dirname(current_path) + if parent_path == current_path: + break + current_path = parent_path + return None + def get_venv_folder_name(): From 3a7a9e69c06eaaa3db3f5044ed50d6d2075a9c5d Mon Sep 17 00:00:00 2001 From: Naggafin Date: Mon, 30 Dec 2024 20:08:24 -0500 Subject: [PATCH 6/6] forgot to remove the original declaration, oops --- django_fastdev/apps.py | 49 ++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/django_fastdev/apps.py b/django_fastdev/apps.py index 16cc19c..db8281b 100644 --- a/django_fastdev/apps.py +++ b/django_fastdev/apps.py @@ -104,6 +104,27 @@ def get_venv_path(): return sys.prefix # 4. look for `pyvenv.cfg` in parent directories of `sys.executable` + def find_pyvenv_cfg(start_path): + """ + Recursively search for `pyvenv.cfg` in the given directory and its parents. + + Args: + start_path (str): The directory to start searching from. + + Returns: + str or None: The path to `pyvenv.cfg` if found, or None otherwise. + """ + current_path = start_path + while True: + potential_cfg = os.path.join(current_path, "pyvenv.cfg") + if os.path.isfile(potential_cfg): + return potential_cfg + parent_path = os.path.dirname(current_path) + if parent_path == current_path: + break + current_path = parent_path + return None + venv_cfg_path = find_pyvenv_cfg(os.path.dirname(sys.executable)) if venv_cfg_path: return os.path.dirname(venv_cfg_path) @@ -112,34 +133,6 @@ def get_venv_path(): return None -def find_pyvenv_cfg(start_path): - """ - Recursively search for `pyvenv.cfg` in the given directory and its parents. - - Args: - start_path (str): The directory to start searching from. - - Returns: - str or None: The path to `pyvenv.cfg` if found, or None otherwise. - """ - current_path = start_path - while True: - potential_cfg = os.path.join(current_path, "pyvenv.cfg") - if os.path.isfile(potential_cfg): - return potential_cfg - parent_path = os.path.dirname(current_path) - if parent_path == current_path: - break - current_path = parent_path - return None - - - -def get_venv_folder_name(): - venv_path = get_venv_path() - return os.path.basename(venv_path) if venv_path else venv_path - - def is_absolute_url(url): return bool(url.startswith('/') or url.startswith('http://') or url.startswith('https://'))