Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AttributeError: 'SafeApplication' object has no attribute 'project' #1468

Closed
Tobi-De opened this issue May 6, 2024 · 15 comments · May be fixed by juftin/hatch-pip-compile#80
Closed

AttributeError: 'SafeApplication' object has no attribute 'project' #1468

Tobi-De opened this issue May 6, 2024 · 15 comments · May be fixed by juftin/hatch-pip-compile#80

Comments

@Tobi-De
Copy link

Tobi-De commented May 6, 2024

I wasn't sure what to title this issue, but it seems to be a regression because I feel like it wasn't happening with the previous version of hatch.
If I have a custom environment, let's say docs, that inherits from the default environment which was not created beforehand, and I try to run a command in the docs environment, it will fail with the error below.
This issue still happens, for example, if I have two custom environments, dev and test, with test inheriting from dev, if the dev environment is not created beforehand, then trying to run a command in the test environment will result in the same error. If I'm not mistaken, previously hatch would create the base environment if there is one before trying to run the command.

Run hatch run test:test
Syncing environment plugin requirements
Creating environment: test
Installing project in development mode
╭───────────────────── Traceback (most recent call last) ──────────────────────╮
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/cl │
│ i/__init__.py:221 in main                                                    │
│                                                                              │
│   218                                                                        │
│   219 def main():  # no cov                                                  │
│   220 │   try:                                                               │
│ ❱ 221 │   │   hatch(prog_name='hatch', windows_expand_args=False)            │
│   222 │   except Exception:  # noqa: BLE001                                  │
│   223 │   │   import sys                                                     │
│   224                                                                        │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/co │
│ re.py:1157 in __call__                                                       │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/co │
│ re.py:1078 in main                                                           │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/co │
│ re.py:1688 in invoke                                                         │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/co │
│ re.py:1434 in invoke                                                         │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/co │
│ re.py:783 in invoke                                                          │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/de │
│ corators.py:33 in new_func                                                   │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/cl │
│ i/run/__init__.py:141 in run                                                 │
│                                                                              │
│   [13](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:14)8 │   elif not env_name:                                                 │
│   139 │   │   env_name = 'system'                                            │
│   [14](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:15)0 │                                                                      │
│ ❱ 141 │   ctx.invoke(                                                        │
│   142 │   │   run_command,                                                   │
│   143 │   │   args=[command, *final_args],                                   │
│   144 │   │   env_names=[env_name],                                          │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/co │
│ re.py:783 in invoke                                                          │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/click/de │
│ corators.py:45 in new_func                                                   │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/cl │
│ i/env/run.py:128 in run                                                      │
│                                                                              │
│   125 │   elif not matrix_selected and (included_variables or excluded_varia │
│   126 │   │   app.abort(f'Variable selection is unsupported for non-matrix e │
│   127 │                                                                      │
│ ❱ 128 │   for context in app.runner_context(                                 │
│   129 │   │   environments,                                                  │
│   130 │   │   ignore_compat=ignore_compat or matrix_selected,                │
│   131 │   │   display_header=matrix_selected,                                │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/cl │
│ i/application.py:224 in runner_context                                       │
│                                                                              │
│   221 │   │   │   │   context = ExecutionContext(environment)                │
│   222 │   │   │   │   yield context                                          │
│   223 │   │   │   │                                                          │
│ ❱ 224 │   │   │   │   self.prepare_environment(environment)                  │
│   225 │   │   │   │   with EnvVars(context.env_vars):                        │
│   226 │   │   │   │   │   self.run_shell_commands(context)                   │
│   227                                                                        │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/cl │
│ i/application.py:143 in prepare_environment                                  │
│                                                                              │
│   140 │   │   │   │   │   │   )                                              │
│   141 │   │                                                                  │
│   142 │   │   with environment.app_status_dependency_state_check():          │
│ ❱ 143 │   │   │   new_dep_hash = environment.dependency_hash()               │
│   144 │   │                                                                  │
│   145 │   │   current_dep_hash = self.env_metadata.dependency_hash(environme │
│   146 │   │   if new_dep_hash != current_dep_hash:                           │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch_pi │
│ p_compile/plugin.py:104 in dependency_hash                                   │
│                                                                              │
│   101 │   │   """                                                            │
│   102 │   │   Get the dependency hash                                        │
│   103 │   │   """                                                            │
│ ❱ 104 │   │   self.run_pip_compile()                                         │
│   105 │   │   hatch_hash = super().dependency_hash()                         │
│   106 │   │   if not self.dependencies:                                      │
│   107 │   │   │   return hatch_hash                                          │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch_pi │
│ p_compile/plugin.py:117 in run_pip_compile                                   │
│                                                                              │
│   114 │   │   Run pip-compile if necessary                                   │
│   1[15](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:16) │   │   """                                                            │
│   1[16](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:17) │   │   self.prepare_environment()                                     │
│ ❱ 117 │   │   if not self.lockfile_up_to_date:                               │
│   118 │   │   │   with self.safe_activation():                               │
│   119 │   │   │   │   self.resolver.install_pypi_dependencies()              │
│   120 │   │   │   │   if self.piptools_lock_file.exists():                   │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/functools.py:995 in    │
│ __get__                                                                      │
│                                                                              │
│    992 │   │   │   raise TypeError(msg) from None                            │
│    993 │   │   val = cache.get(self.attrname, _NOT_FOUND)                    │
│    994 │   │   if val is _NOT_FOUND:                                         │
│ ❱  995 │   │   │   val = self.func(instance)                                 │
│    996 │   │   │   try:                                                      │
│    997 │   │   │   │   cache[self.attrname] = val                            │
│    998 │   │   │   except TypeError:                                         │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch_pi │
│ p_compile/plugin.py:200 in lockfile_up_to_date                               │
│                                                                              │
│   197 │   │   if not self.dependencies and not self.piptools_lock_file.exist │
│   198 │   │   │   return True                                                │
│   199 │   │   if self.piptools_constraints_file:                             │
│ ❱ 200 │   │   │   valid_constraint = self.validate_constraints_file(         │
│   201 │   │   │   │   constraints_file=self.piptools_constraints_file, envir │
│   202 │   │   │   )                                                          │
│   203 │   │   │   if not valid_constraint:                                   │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch_pi │
│ p_compile/plugin.py:325 in validate_constraints_file                         │
│                                                                              │
│   322 │   │   │   │   requirements=environment.dependencies_complex          │
│   323 │   │   │   )                                                          │
│   324 │   │   │   if not up_to_date:                                         │
│ ❱ 325 │   │   │   │   self.constraint_env.run_pip_compile()                  │
│   326 │   │   │   │   return False                                           │
│   327 │   │   return True                                                    │
│   328                                                                        │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch_pi │
│ p_compile/plugin.py:116 in run_pip_compile                                   │
│                                                                              │
│   113 │   │   """                                                            │
│   114 │   │   Run pip-compile if necessary                                   │
│   115 │   │   """                                                            │
│ ❱ 116 │   │   self.prepare_environment()                                     │
│   1[17](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:18) │   │   if not self.lockfile_up_to_date:                               │
│   118 │   │   │   with self.safe_activation():                               │
│   119 │   │   │   │   self.resolver.install_pypi_dependencies()              │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch_pi │
│ p_compile/plugin.py:367 in prepare_environment                               │
│                                                                              │
│   364 │   │   but the `Application` class is not exposed to the environment  │
│   365 │   │   """                                                            │
│   366 │   │   if not self.virtualenv_exists():                               │
│ ❱ 367 │   │   │   self.create()                                              │
│   368 │   │   │   if not self.dependencies_in_sync():                        │
│   369 │   │   │   │   self.sync_dependencies()                               │
│   370 │   │   │   if not self.skip_install:                                  │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/en │
│ v/virtual.py:157 in create                                                   │
│                                                                              │
│   154 """                                                                    │
│   155 │   │   │   │   )                                                      │
│   156 │   │                                                                  │
│ ❱ 157 │   │   with self.expose_uv():                                         │
│   158 │   │   │   self.virtual_env.create(self.parent_python, allow_system_p │
│   159 │                                                                      │
│   160 │   def remove(self):                                                  │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/en │
│ v/virtual.py:99 in expose_uv                                                 │
│                                                                              │
│    96 │   │   return UVVirtualEnv if self.use_uv else VirtualEnv             │
│    97 │                                                                      │
│    98 │   def expose_uv(self):                                               │
│ ❱  99 │   │   if not (self.use_uv or self.uv_path):                          │
│   100 │   │   │   return nullcontext()                                       │
│   101 │   │                                                                  │
│   102 │   │   return EnvVars({'HATCH_UV': self.uv_path})                     │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/functools.py:995 in    │
│ __get__                                                                      │
│                                                                              │
│    992 │   │   │   raise TypeError(msg) from None                            │
│    993 │   │   val = cache.get(self.attrname, _NOT_FOUND)                    │
│    994 │   │   if val is _NOT_FOUND:                                         │
│ ❱  995 │   │   │   val = self.func(instance)                                 │
│    996 │   │   │   try:                                                      │
│    997 │   │   │   │   cache[self.attrname] = val                            │
│    998 │   │   │   except TypeError:                                         │
│                                                                              │
│ /opt/hostedtoolcache/Python/3.12.3/x64/lib/python3.12/site-packages/hatch/en │
│ v/virtual.py:116 in uv_path                                                  │
│                                                                              │
│   113 │   │   │   # Prevent recursive loop                                   │
│   114 │   │   │   self.name == env_name                                      │
│   115 │   │   │   # Only if dependencies have been set by the user           │
│ ❱ 116 │   │   │   or is_default_environment(env_name, self.app.project.confi │
│   117 │   │   ):                                                             │
│   1[18](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:19) │   │   │   uv_env = self.app.get_environment(env_name)                │
│   1[19](https://github.com/Tobi-De/pmprep/actions/runs/8965839700/job/24620124542#step:6:20) │   │   │   self.app.prepare_environment(uv_env)                       │
╰──────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'SafeApplication' object has no attribute 'project'
Error: Process completed with exit code 1.

This is part of the pyproject.toml of the project that generate the error above, I'm using hatch-pip-compile but I doubt it has anything to do with this issue.
I remove the dependencies key since it was not relevant

[tool.hatch.envs.default]
type = "pip-compile"
pip-compile-constraint = "default"
pip-compile-installer = "uv"
pip-compile-resolver = "uv"

[tool.hatch.envs.dev]
lock-filename = "requirements-dev.txt"

[tool.hatch.envs.dev.scripts]
runserver = ["migrate", "python manage.py tailwind runserver {args}"]
migrate = "python manage.py migrate {args}"
makemigrations = "python manage.py makemigrations {args}"
reset-db = "python manage.py reset_db --noinput"
shell = "python manage.py shell_plus {args}"

[tool.hatch.envs.docs]
lock-filename = "docs/requirements.txt"
@Tobi-De
Copy link
Author

Tobi-De commented May 6, 2024

I managed to get the same error (or a similar one, not sure) with all environnments already created.
I have a dev environnment, with mypy installed in it, first I activated the env with hatch shell dev (btw love this feature, was hoping to see this work some day), if I run mypy . it works but if I run hatch run dev:mypy . if fails with the error below

│ /Users/tobi/Library/Application                                                                  │
│ Support/pyapp/hatch/554652334309206614/1.9.3/lib/python3.11/site-packages/hatch/env/virtual.py:1 │
│ 16 in uv_path                                                                                    │
│                                                                                                  │
│   113 │   │   │   # Prevent recursive loop                                                       │
│   114 │   │   │   self.name == env_name                                                          │
│   115 │   │   │   # Only if dependencies have been set by the user                               │
│ ❱ 116 │   │   │   or is_default_environment(env_name, self.app.project.config.internal_envs[en   │
│   117 │   │   ):                                                                                 │
│   118 │   │   │   uv_env = self.app.get_environment(env_name)                                    │
│   119 │   │   │   self.app.prepare_environment(uv_env)                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
AttributeError: 'SafeApplication' object has no attribute 'project'

@ofek
Copy link
Sponsor Collaborator

ofek commented May 6, 2024

Latest version of that plugin?

@ofek
Copy link
Sponsor Collaborator

ofek commented May 6, 2024

@juftin

@Tobi-De
Copy link
Author

Tobi-De commented May 6, 2024

Latest version of that plugin?

Yes

@juftin
Copy link
Contributor

juftin commented May 6, 2024

Thanks for the mention @ofek 🙏 @Tobi-De I will get a look at this as soon as I can and follow up with some debugging steps once I have an idea. I'm actually on a business / vacation trip right now in Europe from the US and a bit jet-lagged so no promises on exact ETA to resolution, I'll do my best for something quick.

@Tobi-De
Copy link
Author

Tobi-De commented May 6, 2024

I'm using hatch-pip-compile but I doubt it has anything to do with this issue.

Silly me, now that I've taken a closer look at the traceback, I've realized that yes, it's related to hatch-pip-compile. Sorry for bothering you @ofek

I'm actually on a business / vacation trip right now in Europe from the US and a bit jet-lagged so no promises on exact ETA to resolution, I'll do my best for something quick.

Don't worry, it's just annoying, it doesn't stop me from working.

@juftin
Copy link
Contributor

juftin commented May 12, 2024

😅 I'm still out on vacation but it's looking like hatch 1.10.x created a regression with hatch-pip-compile. Ultimately the issue occurs when an environment that doesn't exist tries to create itself.

I think this has to do with the new uv functionality - it's got something to do with hatch-pip-compile receiving a hatchling.bridge.app.Application when it actually is expecting a hatch.cli.Application:

@property
def app(self):
"""
An instance of [Application](../utilities.md#hatchling.bridge.app.Application).
"""
if self.__app is None:
from hatchling.bridge.app import Application
self.__app = Application().get_safe_application()
return self.__app

I'm debugging on a train in Portugal right now and it's slow going... I'll continue to provide more details.

EDIT I'm working on this over at juftin/hatch-pip-compile#80 where I've got a minimal reproducible example

@juftin
Copy link
Contributor

juftin commented May 16, 2024

Hey @ofek, just curious what what the recursive loop issue here? https://github.com/pypa/hatch/blame/6a147fc1d6e8f5c9e04ccbe28201b71a783f2ec8/src/hatch/env/virtual.py#L113

I ran into my own recursive error when trying to resolve this issue

@juftin
Copy link
Contributor

juftin commented May 16, 2024

I fel like I'm on the right track to resolve this here - if I could replace the hatchling.bridge.app.Application with a hatch.cli.Application it would be even easier...

@property
def app(self):
"""
An instance of [Application](../utilities.md#hatchling.bridge.app.Application).
"""
if self.__app is None:
from hatchling.bridge.app import Application
self.__app = Application().get_safe_application()
return self.__app

@ofek
Copy link
Sponsor Collaborator

ofek commented May 16, 2024

  • The issue is that the environment that provides UV necessarily can't use the UV that it provides to create itself.
  • I don't have time to look because I'm preparing to travel for my talk at PyCon but I think that only exists now because it would involve changing arguments used in many tests. If you have the courage please do that!

@ofek
Copy link
Sponsor Collaborator

ofek commented May 25, 2024

Can this be closed?

@Tobi-De Tobi-De closed this as completed May 25, 2024
@juftin
Copy link
Contributor

juftin commented May 28, 2024

@ofek I would love to get to a place where this code block uses a hatch.cli.Application instead of a hatchling.bridge.app.Application

@property
def app(self):
"""
An instance of [Application](../utilities.md#hatchling.bridge.app.Application).
"""
if self.__app is None:
from hatchling.bridge.app import Application
self.__app = Application().get_safe_application()
return self.__app

Currently lines 99-101 are uncovered in hatch's test suite and are only surfacing in hatch-pip-compile because of the way it generates a PipCompileEnvironment instance when it needs to check on a constraint environment.

I've tried to look into making a hatch PR for this but am a little lost, curious if you think switching over to a hatch.cli.Application would be a reasonable bit of work?

(FWIW I believe I've come up with a solution to this but it breaks a big part of my unit tests which switching to a hatch.cli.Application would resolve)

@ofek
Copy link
Sponsor Collaborator

ofek commented May 28, 2024

I was unaware that self.__app could be unset outside of the test suite. Can you show how this is happening to you?

@juftin
Copy link
Contributor

juftin commented May 28, 2024

Basically I'm creating an environment outside of the context of a hatch.cli.Application https://github.com/juftin/hatch-pip-compile/blob/fix/hatch_110_regression/tests/conftest.py#L136-L146

I have a large test fixture (called pip_compile) that allows me to run things like this

assert original_requirements == [packaging.requirements.Requirement("hatch")]
pip_compile.toml_doc["project"]["dependencies"] = ["requests"]
pip_compile.update_pyproject()
test_environment = pip_compile.reload_environment("test")
pip_compile.application.prepare_environment(environment=test_environment)
new_lockfile_requirements = (
    pip_compile.default_environment.piptools_lock.read_header_requirements()
)
assert new_lockfile_requirements == [packaging.requirements.Requirement("requests")]

It's all a bit hacky, but basically I would like to test scenarios where an environment is created and all dependencies are installed, then a dependency changes and the environment is invoked again and it recognizes that new dependencies need to be installed (and the lockfile is regenerated).

All of this hackiness worked before hatch 1.10.x - you can see that the test suite succeeds for older versions of hatch https://github.com/juftin/hatch-pip-compile/actions/runs/9226545268/job/25386557544?pr=80

If swapping a hatch.cli.Application instead of a hatchling.bridge.app.Application is a large lift I can look at rewriting my test suite - I just figured I would bring this up to you in case it was easy.

@ofek
Copy link
Sponsor Collaborator

ofek commented May 28, 2024

  • I don't have time to look because I'm preparing to travel for my talk at PyCon but I think that only exists now because it would involve changing arguments used in many tests. If you have the courage please do that!

Like I said there I think literally the only reason is because of the test suite and procrastinating changing that much because a simple find/replace wouldn't quite be enough. In the example you showed you can just pass a real application to the environment like I would have to do for the test suite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants