Repository: dbt-labs/dbt-core

Issue:
[CT-680] [Feature] Support for .py files when using file selectors
### Is this your first time opening an issue?

- [X] I have read the [expectations for open source contributors](https://docs.getdbt.com/docs/contributing/oss-expectations)

### Describe the Feature

> dbt should just know that when i say: dbt run -m my_model.sql i mean: dbt run -m my_model
> 
> https://twitter.com/pedram_navid/status/1519160872588898304

but for `.py` files, since [dbt Python models are gonna happen](https://github.com/dbt-labs/dbt-core/discussions/5261).

I would edit [this code](https://github.com/dbt-labs/dbt-core/blob/main/core/dbt/graph/selector_spec.py#L83), as well as (I think?) [this test](https://github.com/jwills/dbt-core/blob/1510eeb06a4479f5e74cac645f3b19d10fecaf85/test/unit/test_graph_selector_methods.py#L708).

### Describe alternatives you've considered

Life would go on without this feature, but why should we settle for less when a first-time contributor (me) wants to build it.

### Who will this benefit?

- > Pedram, apparently
- Me, because I'll gain experience by making a small contribution to `dbt-core`.
- The community, because with my newly gained experience, I'll be able to help with more, and more complex, issues over time.
- There are times when it's easier to include the file extension in the `dbt run` command. For example, if you're generating the `dbt run` command from a script, and don't want to bother stripping the file extensions from the list of models you plan to run.

### Are you interested in contributing this feature?

I'd like to create the PR, but i'll take any help / suggestions / guidance

### Anything else?

- [Josh's issue](https://github.com/dbt-labs/dbt-core/issues/5240) adding file selectors and `.sql` support.
- [Josh's PR](https://github.com/dbt-labs/dbt-core/pull/5241) solving that issue
- Wherein @jwills [suggested](https://github.com/dbt-labs/dbt-core/discussions/5261#discussioncomment-2771140), albeit with a winky face, I add support for `.py` files.


Discussion:

Resolving patch:
diff --git a/core/dbt/graph/selector_spec.py b/core/dbt/graph/selector_spec.py
--- a/core/dbt/graph/selector_spec.py
+++ b/core/dbt/graph/selector_spec.py
@@ -80,7 +80,7 @@ def __post_init__(self):
     def default_method(cls, value: str) -> MethodName:
         if _probably_path(value):
             return MethodName.Path
-        elif value.lower().endswith(".sql"):
+        elif value.lower().endswith((".sql", ".py")):
             return MethodName.File
         else:
             return MethodName.FQN


Test patch:
diff --git a/test/unit/test_graph_selector_methods.py b/test/unit/test_graph_selector_methods.py
--- a/test/unit/test_graph_selector_methods.py
+++ b/test/unit/test_graph_selector_methods.py
@@ -473,6 +473,19 @@ def table_model(ephemeral_model):
     )
 
 
+@pytest.fixture
+def table_model_py(seed):
+    return make_model(
+        'pkg',
+        'table_model_py',
+        'select * from {{ ref("seed") }}',
+        config_kwargs={'materialized': 'table'},
+        refs=[seed],
+        tags=[],
+        path='subdirectory/table_model.py'
+    )
+
+
 @pytest.fixture
 def ext_source():
     return make_source(
@@ -533,7 +546,6 @@ def union_model(seed, ext_source):
         tags=['unions'],
     )
 
-
 @pytest.fixture
 def table_id_unique(table_model):
     return make_unique_test('pkg', table_model, 'id')
@@ -590,13 +602,12 @@ def namespaced_union_model(seed, ext_source):
         tags=['unions'],
     )
 
-
 @pytest.fixture
-def manifest(seed, source, ephemeral_model, view_model, table_model, ext_source, ext_model, union_model, ext_source_2, 
+def manifest(seed, source, ephemeral_model, view_model, table_model, table_model_py, ext_source, ext_model, union_model, ext_source_2, 
     ext_source_other, ext_source_other_2, table_id_unique, table_id_not_null, view_id_unique, ext_source_id_unique, 
     view_test_nothing, namespaced_seed, namespace_model, namespaced_union_model, macro_test_unique, macro_default_test_unique,
     macro_test_not_null, macro_default_test_not_null):
-    nodes = [seed, ephemeral_model, view_model, table_model, union_model, ext_model,
+    nodes = [seed, ephemeral_model, view_model, table_model, table_model_py, union_model, ext_model,
              table_id_unique, table_id_not_null, view_id_unique, ext_source_id_unique, view_test_nothing,
              namespaced_seed, namespace_model, namespaced_union_model]
     sources = [source, ext_source, ext_source_2,
@@ -634,7 +645,7 @@ def test_select_fqn(manifest):
     assert not search_manifest_using_method(manifest, method, 'ext.unions')
     # sources don't show up, because selection pretends they have no FQN. Should it?
     assert search_manifest_using_method(manifest, method, 'pkg') == {
-        'union_model', 'table_model', 'view_model', 'ephemeral_model', 'seed',
+        'union_model', 'table_model', 'table_model_py', 'view_model', 'ephemeral_model', 'seed',
         'mynamespace.union_model', 'mynamespace.ephemeral_model', 'mynamespace.seed'}
     assert search_manifest_using_method(
         manifest, method, 'ext') == {'ext_model'}
@@ -713,10 +724,14 @@ def test_select_file(manifest):
 
     assert search_manifest_using_method(
         manifest, method, 'table_model.sql') == {'table_model'}
+    assert search_manifest_using_method(
+        manifest, method, 'table_model.py') == {'table_model_py'}
     assert search_manifest_using_method(
         manifest, method, 'union_model.sql') == {'union_model', 'mynamespace.union_model'}
     assert not search_manifest_using_method(
         manifest, method, 'missing.sql')
+    assert not search_manifest_using_method(
+        manifest, method, 'missing.py')
 
 
 def test_select_package(manifest):
@@ -725,7 +740,7 @@ def test_select_package(manifest):
     assert isinstance(method, PackageSelectorMethod)
     assert method.arguments == []
 
-    assert search_manifest_using_method(manifest, method, 'pkg') == {'union_model', 'table_model', 'view_model', 'ephemeral_model',
+    assert search_manifest_using_method(manifest, method, 'pkg') == {'union_model', 'table_model', 'table_model_py', 'view_model', 'ephemeral_model',
                                                                      'seed', 'raw.seed', 'unique_table_model_id', 'not_null_table_model_id', 'unique_view_model_id', 'view_test_nothing',
                                                                      'mynamespace.seed', 'mynamespace.ephemeral_model', 'mynamespace.union_model',
                                                                      }
@@ -744,7 +759,7 @@ def test_select_config_materialized(manifest):
     assert search_manifest_using_method(manifest, method, 'view') == {
         'view_model', 'ext_model'}
     assert search_manifest_using_method(manifest, method, 'table') == {
-        'table_model', 'union_model', 'mynamespace.union_model'}
+        'table_model', 'table_model_py', 'union_model', 'mynamespace.union_model'}
 
 
 def test_select_test_name(manifest):