1from __future__ import annotations
2
3import importlib.metadata
4
5
6__all__ = ["tag", "version", "commit"]
7
8
9# ========= =========== ===================
10# release development
11# ========= =========== ===================
12# tag X.Y X.Y (upcoming)
13# version X.Y X.Y.dev1+g5678cde
14# commit X.Y 5678cde
15# ========= =========== ===================
16
17
18# When tagging a release, set `released = True`.
19# After tagging a release, set `released = False` and increment `tag`.
20
21released = False
22
23tag = version = commit = "15.1"
24
25
26if not released: # pragma: no cover
27 import pathlib
28 import re
29 import subprocess
30
31 def get_version(tag: str) -> str:
32 # Since setup.py executes the contents of src/websockets/version.py,
33 # __file__ can point to either of these two files.
34 file_path = pathlib.Path(__file__)
35 root_dir = file_path.parents[0 if file_path.name == "setup.py" else 2]
36
37 # Read version from package metadata if it is installed.
38 try:
39 version = importlib.metadata.version("websockets")
40 except ImportError:
41 pass
42 else:
43 # Check that this file belongs to the installed package.
44 files = importlib.metadata.files("websockets")
45 if files:
46 version_files = [f for f in files if f.name == file_path.name]
47 if version_files:
48 version_file = version_files[0]
49 if version_file.locate() == file_path:
50 return version
51
52 # Read version from git if available.
53 try:
54 description = subprocess.run(
55 ["git", "describe", "--dirty", "--tags", "--long"],
56 capture_output=True,
57 cwd=root_dir,
58 timeout=1,
59 check=True,
60 text=True,
61 ).stdout.strip()
62 # subprocess.run raises FileNotFoundError if git isn't on $PATH.
63 except (
64 FileNotFoundError,
65 subprocess.CalledProcessError,
66 subprocess.TimeoutExpired,
67 ):
68 pass
69 else:
70 description_re = r"[0-9.]+-([0-9]+)-(g[0-9a-f]{7,}(?:-dirty)?)"
71 match = re.fullmatch(description_re, description)
72 if match is None:
73 raise ValueError(f"Unexpected git description: {description}")
74 distance, remainder = match.groups()
75 remainder = remainder.replace("-", ".") # required by PEP 440
76 return f"{tag}.dev{distance}+{remainder}"
77
78 # Avoid crashing if the development version cannot be determined.
79 return f"{tag}.dev0+gunknown"
80
81 version = get_version(tag)
82
83 def get_commit(tag: str, version: str) -> str:
84 # Extract commit from version, falling back to tag if not available.
85 version_re = r"[0-9.]+\.dev[0-9]+\+g([0-9a-f]{7,}|unknown)(?:\.dirty)?"
86 match = re.fullmatch(version_re, version)
87 if match is None:
88 raise ValueError(f"Unexpected version: {version}")
89 (commit,) = match.groups()
90 return tag if commit == "unknown" else commit
91
92 commit = get_commit(tag, version)