11# Use with https://www.pyinvoke.org/
22
3- from invoke import call , task
3+ from invoke import task # ty:ignore[unresolved-import]
44
55import os
66import pathlib
1010os .chdir (pathlib .Path (__file__ ).parent )
1111
1212
13- VENV_DIR = pathlib .Path (".venv" )
14- REQ_TXT = pathlib .Path ("requirements.txt" )
15-
1613PRIV_DIR = pathlib .Path ("~/private" ).expanduser ()
1714STAGING_CREDENTIALS = PRIV_DIR / "gcloud-creds/vimhelp-staging-owner.json"
1815
2421 "PYTHONWARNINGS" : (
2522 "default,"
2623 "ignore:unclosed:ResourceWarning:sys,"
27- "ignore:Type google._upb._message:DeprecationWarning:importlib._bootstrap,"
2824 "ignore:This process (pid=:DeprecationWarning:gevent.os"
2925 ),
3026 "VIMHELP_ENV" : "dev" ,
3430}
3531
3632
37- @task (help = {"lazy" : "Only update venv if out-of-date wrt requirements.txt" })
38- def venv (c , lazy = False ):
39- """Populate virtualenv."""
40- if not VENV_DIR .exists ():
41- c .run (f"python -m venv --upgrade-deps { VENV_DIR } " )
42- c .run (f"{ VENV_DIR } /bin/pip install -U wheel" )
43- print ("Created venv." )
44- lazy = False
45- if not lazy or REQ_TXT .stat ().st_mtime > VENV_DIR .stat ().st_mtime :
46- c .run (f"{ VENV_DIR } /bin/pip install -U --upgrade-strategy eager -r { REQ_TXT } " )
47- c .run (f"touch { VENV_DIR } " )
48- print ("Updated venv." )
49- else :
50- print ("venv was already up-to-date." )
51-
52-
53- venv_lazy = call (venv , lazy = True )
54-
55-
5633@task
5734def lint (c ):
58- """Run linter/formatter (ruff)."""
59- c .run ("ruff check ." )
60- c .run ("ruff format --check" )
35+ """Run linters."""
36+ c .run ("uv sync --locked" )
37+ c .run ("ruff check ." , pty = True )
38+ c .run ("ruff format --check" , pty = True )
39+ c .run ("ty check" , pty = True )
6140
6241
6342@task (
64- pre = [venv_lazy ],
6543 help = {
6644 "gunicorn" : "Run using gunicorn instead of 'flask run'" ,
6745 "tracemalloc" : "Run with tracemalloc enabled" ,
@@ -71,21 +49,21 @@ def run(c, gunicorn=False, tracemalloc=False):
7149 """Run app locally against staging database."""
7250 _ensure_private_mount (c )
7351 if gunicorn :
74- cmd = f" { VENV_DIR } /bin/ gunicorn -c gunicorn.conf.dev.py"
52+ cmd = "uv run gunicorn -c gunicorn.conf.dev.py"
7553 else :
76- cmd = f" { VENV_DIR } /bin/ flask --app vimhelp.webapp --debug run"
54+ cmd = "uv run flask --app vimhelp.webapp --debug run"
7755 if tracemalloc :
7856 env = DEV_ENV | {"PYTHONTRACEMALLOC" : "1" }
7957 else :
8058 env = DEV_ENV
81- c .run (cmd , env = env )
59+ c .run (cmd , env = env , pty = True )
8260
8361
84- @task ( pre = [ venv_lazy ])
62+ @task
8563def show_routes (c ):
8664 """Show Flask routes."""
8765 _ensure_private_mount (c )
88- c .run (f" { VENV_DIR } /bin/ flask --app vimhelp.webapp --debug routes" , env = DEV_ENV )
66+ c .run ("uv run flask --app vimhelp.webapp --debug routes" , env = DEV_ENV , pty = True )
8967
9068
9169@task (
@@ -98,27 +76,46 @@ def show_routes(c):
9876) # fmt: skip
9977def deploy (c , target = "staging" , cron = False ):
10078 """Deploy app."""
79+ c .run ("uv export -q --locked --no-emit-project -o requirements.txt" )
10180 _ensure_private_mount (c )
102- if target == "all" :
103- targets = "staging" , "prod"
104- else :
105- targets = (target ,)
106- for t in targets :
107- if t == "staging" :
108- cmd = f"gcloud app deploy --quiet --project={ PROJECT_STAGING } "
109- elif t == "prod" :
110- cmd = f"gcloud app deploy --project={ PROJECT_PROD } "
111- else :
112- sys .exit (f"Invalid target name: '{ t } '" )
81+ target_map = {
82+ "all" : (PROJECT_STAGING , PROJECT_PROD ),
83+ "staging" : (PROJECT_STAGING ,),
84+ "prod" : (PROJECT_PROD ,),
85+ }
86+ projects = target_map .get (target )
87+ if projects is None :
88+ sys .exit (f"Invalid target name: '{ target } '" )
89+ for p in projects : # ty:ignore[not-iterable]
90+ cmd = f"gcloud app deploy --project={ p } --quiet"
11391 if cron :
11492 cmd += " cron.yaml"
11593 c .run (cmd , pty = True )
11694
95+ old_vers = c .run (
96+ f"gcloud app versions list --project={ p } --format='value(id)' "
97+ "--filter='traffic_split=0'"
98+ ).stdout .split ()
99+ if len (old_vers ) > 0 :
100+ print ("Deleting old version(s):" , ", " .join (old_vers ))
101+ c .run (
102+ f"gcloud app versions delete --project={ p } --quiet "
103+ + " " .join (old_vers )
104+ )
105+
117106
118107@task
119108def clean (c ):
120109 """Clean up build artefacts."""
121- for d in VENV_DIR , "__pycache__" , "vimhelp/__pycache__" , ".ruff_cache" :
110+ to_remove = (
111+ ".ruff_cache" ,
112+ ".venv" ,
113+ "__pycache__" ,
114+ "requirements.txt" ,
115+ "vimhelp/__pycache__" ,
116+ "vimhelp.egg-info" ,
117+ )
118+ for d in to_remove :
122119 if pathlib .Path (d ).exists ():
123120 c .run (f"rm -rf { d } " )
124121
@@ -127,7 +124,7 @@ def clean(c):
127124def sh (c ):
128125 """Interactive shell with virtualenv and datastore available."""
129126 _ensure_private_mount (c )
130- with c .prefix (f ". { VENV_DIR } /bin/activate" ):
127+ with c .prefix (". .venv /bin/activate" ):
131128 c .run (os .getenv ("SHELL" , "bash" ), env = DEV_ENV , pty = True )
132129 print ("Exited vimhelp shell" )
133130
0 commit comments