Repository: beeware/briefcase

Issue:
Briefcase `v0.3.12.dev0+xxx` is not Treated as a Dev Version
**Describe the bug**
The `dev` attribute of a `packaging.version.Version` object is the number from `dev#` in the version. When the number is `0`, the version is not treated as a dev version.

`create.py`
```python
            # If we're on a development branch, and the template branch was *not*
            # provided explicity, we can use a fallback development template.
            # Otherwise, re-raise the exception about the unsupported template version.
            if version.dev and app.template_branch is None:
```

`new.py`
```python
            # If we're *not* on a development branch, raise an error about
            # the missing template branch.
            if not version.dev:
                raise
```

```python
❯ python
Python 3.10.7 (main, Sep 28 2022, 17:55:46) [GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from packaging.version import Version
>>> Version('0.3.12.dev1+gf0e822f.d20221016').dev
1
>>> Version('0.3.12.dev0+gf0e822f.d20221016').dev
0
```

**To Reproduce**
Steps to reproduce the behavior:
Run `briefcase create` or `briefcase new` with a `dev0` version.

**Expected behavior**
`dev0` versions are dev versions.

**Environment:**
 - Operating System: Ubuntu 22
 - Python version: 3.10
 - Software versions:
   - Briefcase: `0.3.12.dev0+gf0e822f.d20221016`

Discussion:

Patch:
diff --git a/changes/926.misc.rst b/changes/926.misc.rst
new file mode 100644
index 00000000..3ca2340a
--- /dev/null
+++ b/changes/926.misc.rst
@@ -0,0 +1,1 @@
+Briefcase versions containing ``dev0`` are now considered development versions for fetching templates.
diff --git a/src/briefcase/commands/create.py b/src/briefcase/commands/create.py
index dfff0ff8..4e8fc9fd 100644
--- a/src/briefcase/commands/create.py
+++ b/src/briefcase/commands/create.py
@@ -291,9 +291,9 @@ class CreateCommand(BaseCommand):
             )
         except TemplateUnsupportedVersion:
             # If we're on a development branch, and the template branch was *not*
-            # provided explicity, we can use a fallback development template.
+            # provided explicitly, we can use a fallback development template.
             # Otherwise, re-raise the exception about the unsupported template version.
-            if version.dev and app.template_branch is None:
+            if version.dev is not None and app.template_branch is None:
                 # Development branches can use the main template.
                 self.logger.info(
                     f"Template branch {template_branch} not found; falling back to development template"
diff --git a/src/briefcase/commands/new.py b/src/briefcase/commands/new.py
index 2abfcfb4..65a2f512 100644
--- a/src/briefcase/commands/new.py
+++ b/src/briefcase/commands/new.py
@@ -503,7 +503,7 @@ What GUI toolkit do you want to use for this project?""",
         except TemplateUnsupportedVersion:
             # If we're *not* on a development branch, raise an error about
             # the missing template branch.
-            if not version.dev:
+            if version.dev is None:
                 raise
 
             # Development branches can use the main template.


Test patch:
diff --git a/tests/commands/create/test_generate_app_template.py b/tests/commands/create/test_generate_app_template.py
index ff2fb356..599553b4 100644
--- a/tests/commands/create/test_generate_app_template.py
+++ b/tests/commands/create/test_generate_app_template.py
@@ -51,11 +51,12 @@ def full_context():
     "briefcase_version, expected_branch",
     [
         ("37.42.1", "v37.42.1"),
-        ("37.42.2.dev73+gad61a29.d20220919", "v37.42.2"),
-        ("37.42.3a1", "v37.42.3"),
-        ("37.42.4b2", "v37.42.4"),
-        ("37.42.5rc3", "v37.42.5"),
-        ("37.42.6.post1", "v37.42.6"),
+        ("37.42.2.dev0+gad61a29.d20220919", "v37.42.2"),
+        ("37.42.3.dev73+gad61a29.d20220919", "v37.42.3"),
+        ("37.42.4a1", "v37.42.4"),
+        ("37.42.5b2", "v37.42.5"),
+        ("37.42.6rc3", "v37.42.6"),
+        ("37.42.7.post1", "v37.42.7"),
     ],
 )
 def test_default_template(
@@ -88,11 +89,21 @@ def test_default_template(
     )
 
 
-def test_default_template_dev(monkeypatch, create_command, myapp, full_context):
+@pytest.mark.parametrize(
+    "briefcase_version",
+    ("37.42.7.dev0+gad61a29.d20220919", "37.42.7.dev73+gad61a29.d20220919"),
+)
+def test_default_template_dev(
+    monkeypatch,
+    create_command,
+    myapp,
+    full_context,
+    briefcase_version,
+):
     """In a dev version, template will fall back to the 'main' branch if a
     versioned template doesn't exist."""
     # Set the Briefcase version
-    monkeypatch.setattr(briefcase, "__version__", "37.42.7.dev73+gad61a29.d20220919")
+    monkeypatch.setattr(briefcase, "__version__", briefcase_version)
 
     # There won't be a cookiecutter cache, so there won't be
     # a cache path (yet).
@@ -128,12 +139,20 @@ def test_default_template_dev(monkeypatch, create_command, myapp, full_context):
     ]
 
 
+@pytest.mark.parametrize(
+    "briefcase_version",
+    ("37.42.7.dev0+gad61a29.d20220919", "37.42.7.dev73+gad61a29.d20220919"),
+)
 def test_default_template_dev_explicit_branch(
-    monkeypatch, create_command, myapp, full_context
+    monkeypatch,
+    create_command,
+    myapp,
+    full_context,
+    briefcase_version,
 ):
     """In a dev version, if an explicit branch is provided, it is used."""
     # Set the Briefcase version
-    monkeypatch.setattr(briefcase, "__version__", "37.42.7.dev73+gad61a29.d20220919")
+    monkeypatch.setattr(briefcase, "__version__", briefcase_version)
 
     # Set an explicit branch
     branch = "some_branch"
@@ -158,13 +177,21 @@ def test_default_template_dev_explicit_branch(
     ]
 
 
+@pytest.mark.parametrize(
+    "briefcase_version",
+    ("37.42.7.dev0+gad61a29.d20220919", "37.42.7.dev73+gad61a29.d20220919"),
+)
 def test_default_template_dev_explicit_invalid_branch(
-    monkeypatch, create_command, myapp, full_context
+    monkeypatch,
+    create_command,
+    myapp,
+    full_context,
+    briefcase_version,
 ):
     """In a dev version, if an explicit (but invalid) branch is provided, the
-    fall back to the 'main' branch will not occur."""
+    fallback to the 'main' branch will not occur."""
     # Set the Briefcase version to a dev version
-    monkeypatch.setattr(briefcase, "__version__", "37.42.7.dev73+gad61a29.d20220919")
+    monkeypatch.setattr(briefcase, "__version__", briefcase_version)
 
     # Set an explicit branch
     branch = "some_branch"
@@ -488,7 +515,11 @@ def test_cached_template(monkeypatch, create_command, myapp, full_context):
 
 
 def test_cached_template_offline(
-    monkeypatch, create_command, myapp, full_context, capsys
+    monkeypatch,
+    create_command,
+    myapp,
+    full_context,
+    capsys,
 ):
     """If the user is offline, a cached template won't be updated, but will
     still work."""
@@ -532,7 +563,10 @@ def test_cached_template_offline(
 
 
 def test_cached_missing_branch_template(
-    monkeypatch, create_command, myapp, full_context
+    monkeypatch,
+    create_command,
+    myapp,
+    full_context,
 ):
     """If the cached repo doesn't have a branch for this Briefcase version, an
     error is raised."""
diff --git a/tests/commands/new/test_new_app.py b/tests/commands/new/test_new_app.py
index 16ead8a8..64eb4cc6 100644
--- a/tests/commands/new/test_new_app.py
+++ b/tests/commands/new/test_new_app.py
@@ -24,11 +24,12 @@ def new_command(tmp_path):
     "briefcase_version, expected_branch",
     [
         ("37.42.1", "v37.42.1"),
-        ("37.42.2.dev73+gad61a29.d20220919", "v37.42.2"),
-        ("37.42.3a1", "v37.42.3"),
-        ("37.42.4b2", "v37.42.4"),
-        ("37.42.5rc3", "v37.42.5"),
-        ("37.42.6.post1", "v37.42.6"),
+        ("37.42.2.dev0+gad61a29.d20220919", "v37.42.2"),
+        ("37.42.3.dev73+gad61a29.d20220919", "v37.42.3"),
+        ("37.42.4a1", "v37.42.4"),
+        ("37.42.5b2", "v37.42.5"),
+        ("37.42.6rc3", "v37.42.6"),
+        ("37.42.7.post1", "v37.42.7"),
     ],
 )
 def test_new_app(
@@ -113,10 +114,14 @@ def test_new_app_missing_template(monkeypatch, new_command, tmp_path):
     )
 
 
-def test_new_app_dev(monkeypatch, new_command, tmp_path):
+@pytest.mark.parametrize(
+    "briefcase_version",
+    ("37.42.7.dev0+gad61a29.d20220919", "37.42.7.dev73+gad61a29.d20220919"),
+)
+def test_new_app_dev(monkeypatch, new_command, tmp_path, briefcase_version):
     """In a dev version, template will fall back to the 'main' branch if a
     versioned template doesn't exist."""
-    monkeypatch.setattr(briefcase, "__version__", "37.42.7.dev73+gad61a29.d20220919")
+    monkeypatch.setattr(briefcase, "__version__", briefcase_version)
     app_context = {
         "formal_name": "My Application",
         "class_name": "MyApplication",