Repository: dbt-labs/dbt-core

Issue:
[CT-1815] [Feature] Remove required 'version' from dbt_project.yml
### Is this your first time submitting a feature request?

- [X] I have read the [expectations for open source contributors](https://docs.getdbt.com/docs/contributing/oss-expectations)
- [X] I have searched the existing issues, and I could not find an existing issue for this feature
- [X] I am requesting a straightforward extension of existing dbt functionality, rather than a Big Idea better suited to a discussion

### Describe the feature

At the moment, it is required that dbt projects include a `version` in their dbt_project.yml, which should be a semantic version, as mentioned in the dbt doc [here](https://docs.getdbt.com/reference/project-configs/version).

My dbt project is a git repo with its own process for creating and publishing its semantic versioning. The semantic version is not hard coded in the code base, but it is generated semi-automatically by our CI/CD. I imagine this is a common practice.

Therefore our dbt project has 2 semantic versions, the one in `dbt_project.yml`, which does not serve any meaningful purpose according to the dbt doc, and the git semantic version (tag).

In summary, I suggest removing the required 'version' from dbt_project.yml, because:
- it does not serve any purpose
- it is confusing as it is potentially in conflict with the git repo's own versioning.

### Describe alternatives you've considered

_No response_

### Who will this benefit?

All?

### Are you interested in contributing this feature?

Sure

### Anything else?

https://docs.getdbt.com/reference/project-configs/version


Discussion:
@seub Thanks for opening! It's a perfectly reasonable point. I can swing back later with a fuller answer.

For now, just wanted to link some relevant thoughts I shared on this a while ago: https://github.com/dbt-labs/dbt-core/issues/4453#issuecomment-994716526

In that comment, I suggested that this `version` field could someday be enforced, and used to unlock something like `dbt deps freeze`. @dbeatty10 and I were just talking the other day about a _different_ mechanism that could hypothetically unlock that — issue(s) TK.
Love this suggestion @seub !

I distinctly remember when a colleague enlightened me that `version` isn't used at all. `dbt_utils` is a great example of a dbt package that just [permanently set a dummy version number of `0.1.0`](https://github.com/dbt-labs/dbt-utils/blob/a6c3159af43e237a6a2057f43461d5040f1193ce/dbt_project.yml#L2) (even though its latest version is `1.0.0` as-of this writing).

Looking forward to hearing more of your thoughts @jtcohen6. (I still need to write-up our discussion we had about that _different_ mechanism -- will drop a link in this thread once I do so.)

Some things for us to consider:
- In our current world, would it be a breaking change if we relax `version` from required to optional? (I suspect "no")
- In a world with `dbt deps freeze`, how would we want to think about the "version" of dbt packages installed via git hash or git tag or tarball URL?
@dbeatty10 Without stealing any thunder from the forthcoming write-up — given that the only good reason I could come up with ~a year ago for continuing to require `version` was, "maybe we'd use this for `deps`" — and given our chat about other better mechanisms for doing that (if we had capacity & appetite) — I struggle to think of any other good _future_ reason to keep requiring `version` today.

A colleague just asked a perfectly reasonable, related question:
> Why, when I configure `config-version: 2` in my dbt project file, do I still need to configure `version: 2` in my yml files?

These are entirely separate versioning schemes. The _reason_ we had to call it `config-version` in `dbt_project.yml`, back in v0.17 when we were making breaking changes to the `vars` structure... is because a field named `version` was already taken! Even though it served no purpose!

@seub So - I'd be open to relaxing this. This will just require a few targeted one-line updates, to make this `Optional` (`None` by default):

https://github.com/dbt-labs/dbt-core/blob/065ab2ebc2104635770f49ca48bc434532d3ef41/core/dbt/contracts/project.py#L187

https://github.com/dbt-labs/dbt-core/blob/065ab2ebc2104635770f49ca48bc434532d3ef41/core/dbt/config/project.py#L528

And we'd definitely want to update these docs: https://docs.getdbt.com/reference/project-configs/version#dbt_projectyml-versions

Would you be interested in contributing that change?
@jtcohen6 Thank you! Yes, let me try to contribute that change.
@seub Excited that you're going to work on that contribution! 🤩 

As promised, here's the write-up for that _different_ mechanism to unlock `dbt deps freeze` that @jtcohen6 and I discussed last week and was mentioned [here](https://github.com/dbt-labs/dbt-core/issues/6603#issuecomment-1382148495):
- https://github.com/dbt-labs/dbt-core/issues/6643
@jtcohen6 @dbeatty10 Thank you for your encouragements! 

I'm stuck trying to get my dev setup to work for integration tests: `make setup-db` gives me errors `error: connection to server at "localhost" (::1), port 5432 failed: FATAL:  role "root" does not exist`. I asked for help on Slack [here](https://getdbt.slack.com/archives/C50NEBJGG/p1674001299554449), just mentioning it here for visibility in case either of you have an idea 🙏 
> I asked for help on Slack [here](https://getdbt.slack.com/archives/C50NEBJGG/p1674001299554449)

We'll see if we can get to the bottom of that error -- I just sent a reply in Slack.
Hi all, I created a [PR](https://github.com/dbt-labs/dbt-core/pull/6711) to make the change. Thanks for your help @jtcohen6 and @dbeatty10 !

I did not add any tests. The current tests all passed. If you'd like me to add a unit or integration test, a tip would be great.

I also created an [issue to update the doc](https://github.com/dbt-labs/docs.getdbt.com/issues/2747).

Resolving patch:
diff --git a/core/dbt/config/project.py b/core/dbt/config/project.py
--- a/core/dbt/config/project.py
+++ b/core/dbt/config/project.py
@@ -525,7 +525,7 @@ def to_dict(self):
 @dataclass
 class Project:
     project_name: str
-    version: Union[SemverString, float]
+    version: Optional[Union[SemverString, float]]
     project_root: str
     profile_name: Optional[str]
     model_paths: List[str]
diff --git a/core/dbt/contracts/project.py b/core/dbt/contracts/project.py
--- a/core/dbt/contracts/project.py
+++ b/core/dbt/contracts/project.py
@@ -184,8 +184,8 @@ class RegistryPackageMetadata(
 @dataclass
 class Project(HyphenatedDbtClassMixin, Replaceable):
     name: Identifier
-    version: Union[SemverString, float]
     config_version: int
+    version: Optional[Union[SemverString, float]] = None
     project_root: Optional[str] = None
     source_paths: Optional[List[str]] = None
     model_paths: Optional[List[str]] = None


Test patch:
diff --git a/core/dbt/tests/fixtures/project.py b/core/dbt/tests/fixtures/project.py
--- a/core/dbt/tests/fixtures/project.py
+++ b/core/dbt/tests/fixtures/project.py
@@ -181,7 +181,6 @@ def dbt_project_yml(project_root, project_config_update, logs_dir):
     project_config = {
         "config-version": 2,
         "name": "test",
-        "version": "0.1.0",
         "profile": "test",
         "log-path": logs_dir,
     }
diff --git a/tests/functional/basic/test_project.py b/tests/functional/basic/test_project.py
new file mode 100644
--- /dev/null
+++ b/tests/functional/basic/test_project.py
@@ -0,0 +1,31 @@
+import pytest
+from dbt.tests.util import run_dbt, update_config_file
+from dbt.exceptions import ProjectContractError
+
+
+class TestProjectYamlVersionMissing:
+    # default dbt_project.yml does not fill version
+
+    def test_empty_version(self, project):
+        run_dbt(["run"], expect_pass=True)
+
+
+class TestProjectYamlVersionValid:
+    @pytest.fixture(scope="class")
+    def project_config_update(self):
+        return {"version": "1.0.0"}
+
+    def test_valid_version(self, project):
+        run_dbt(["run"], expect_pass=True)
+
+
+class TestProjectYamlVersionInvalid:
+    def test_invalid_version(self, project):
+        # we need to run it so the project gets set up first, otherwise we hit the semver error in setting up the test project
+        run_dbt()
+        update_config_file({"version": "invalid"}, "dbt_project.yml")
+        with pytest.raises(ProjectContractError) as excinfo:
+            run_dbt()
+        assert "at path ['version']: 'invalid' is not valid under any of the given schemas" in str(
+            excinfo.value
+        )