docassemblecli3 0.2.2__tar.gz → 0.3.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: docassemblecli3
3
- Version: 0.2.2
3
+ Version: 0.3.6
4
4
  Summary: Multi-platform CLI utility for working with docassemble packages and servers
5
5
  Keywords: docassemble
6
6
  Author-email: Jack Adamson <jackadamson@gmail.com>, Jonathan Pyle <jhpyle@gmail.com>
7
- Requires-Python: >= 3.8
7
+ Requires-Python: >= 3.10
8
8
  Description-Content-Type: text/markdown
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python
@@ -41,7 +41,7 @@ released under the MIT License.
41
41
 
42
42
  ## Prerequisites
43
43
 
44
- This program should only require that you have Python 3.8 installed on your
44
+ This program should only require that you have Python 3.10 installed on your
45
45
  computer, but it was developed and tested with Python 3.12. Please report any
46
46
  bugs or errors you experience.
47
47
 
@@ -172,7 +172,7 @@ works:
172
172
  directory]
173
173
  -c, --config PATH Specify the config file to use or leave it
174
174
  blank to skip using any config file [default:
175
- C:\Users\jacka\.docassemblecli]
175
+ C:\Users\current_user\.docassemblecli]
176
176
  -p, --playground (PROJECT) Install into the default Playground or into the
177
177
  specified Playground project.
178
178
  -r, --restart [yes|no|auto] On package install: yes, force a restart | no,
@@ -262,7 +262,7 @@ works:
262
262
  directory]
263
263
  -c, --config PATH Specify the config file to use or leave it
264
264
  blank to skip using any config file [default:
265
- C:\Users\jacka\.docassemblecli]
265
+ C:\Users\current_user\.docassemblecli]
266
266
  -p, --playground (PROJECT) Install into the default Playground or into the
267
267
  specified Playground project.
268
268
  -a, --api <URL TEXT>... URL of the docassemble server and API key of
@@ -282,11 +282,18 @@ Your package's `.gitignore` file is also used by `watch` to decide which files
282
282
  to ignore. If you don't have a `.gitignore` file in your package, then the
283
283
  default `.gitignore` that `create` makes is used instead. The `.git/` directory
284
284
  and `.gitignore` file are both also ignored by `watch` (note: don't add them to
285
- your `.gitignore`).
285
+ your `.gitignore`). The following directories are always ignored by `watch`:
286
+ `.git`, `__pycache__`, `.mypy_cache`, `.venv`, `.history`, `build`.
287
+
288
+ If you have a `directory` key for the server in your `.docassemblecli` config
289
+ file, it will cause that server to be used if no `server` is provided and the
290
+ `directory` matches the `directory` that `watch` was given
291
+ [default: current directory]. Additionally, if there is a `playground` key for
292
+ that server, it will be used when using `watch`.
286
293
 
287
294
  #### watchdog
288
295
 
289
- The `watch` command now depends on the
296
+ The `watch` command depends on the
290
297
  [watchdog](https://pypi.org/project/watchdog/) Python package. This allows
291
298
  `watch` to work on the following platforms that [watchdog] supports:
292
299
 
@@ -336,6 +343,28 @@ easy to use and will prompt you as necessary.
336
343
  remove Remove a server from the config file.
337
344
  test Test the URL and API key.
338
345
 
346
+ The `.docassemblecli` config file can store additional configuration for each server:
347
+
348
+ - `directory`: If the current or provided directory matches this then this server will be selected
349
+ - `playground`: Default playground project to use if `directory` matches
350
+ - `startup`: If set to "install", automatically installs package when `watch` starts
351
+
352
+ For example, your `.docassemblecli` file might look like this:
353
+
354
+ ```yaml
355
+ - apiurl: https://da.example.com
356
+ apikey: H3PWMKJOIVAXL4PWUJH3HG7EKPFU5GYT
357
+ name: da.example.com
358
+ playground: testing
359
+ directory: /path/to/docassemble-mypackage
360
+ startup: install
361
+ ```
362
+
363
+ With this configuration:
364
+ - `da watch` in `/path/to/docassemble-mypackage` will automatically use this server
365
+ - It will install to the "testing" playground project
366
+ - It will install the package once when `watch` starts
367
+
339
368
  ## How it works
340
369
 
341
370
  The `install` command is just a simple Python script that creates a ZIP file and
@@ -22,7 +22,7 @@ released under the MIT License.
22
22
 
23
23
  ## Prerequisites
24
24
 
25
- This program should only require that you have Python 3.8 installed on your
25
+ This program should only require that you have Python 3.10 installed on your
26
26
  computer, but it was developed and tested with Python 3.12. Please report any
27
27
  bugs or errors you experience.
28
28
 
@@ -153,7 +153,7 @@ works:
153
153
  directory]
154
154
  -c, --config PATH Specify the config file to use or leave it
155
155
  blank to skip using any config file [default:
156
- C:\Users\jacka\.docassemblecli]
156
+ C:\Users\current_user\.docassemblecli]
157
157
  -p, --playground (PROJECT) Install into the default Playground or into the
158
158
  specified Playground project.
159
159
  -r, --restart [yes|no|auto] On package install: yes, force a restart | no,
@@ -243,7 +243,7 @@ works:
243
243
  directory]
244
244
  -c, --config PATH Specify the config file to use or leave it
245
245
  blank to skip using any config file [default:
246
- C:\Users\jacka\.docassemblecli]
246
+ C:\Users\current_user\.docassemblecli]
247
247
  -p, --playground (PROJECT) Install into the default Playground or into the
248
248
  specified Playground project.
249
249
  -a, --api <URL TEXT>... URL of the docassemble server and API key of
@@ -263,11 +263,18 @@ Your package's `.gitignore` file is also used by `watch` to decide which files
263
263
  to ignore. If you don't have a `.gitignore` file in your package, then the
264
264
  default `.gitignore` that `create` makes is used instead. The `.git/` directory
265
265
  and `.gitignore` file are both also ignored by `watch` (note: don't add them to
266
- your `.gitignore`).
266
+ your `.gitignore`). The following directories are always ignored by `watch`:
267
+ `.git`, `__pycache__`, `.mypy_cache`, `.venv`, `.history`, `build`.
268
+
269
+ If you have a `directory` key for the server in your `.docassemblecli` config
270
+ file, it will cause that server to be used if no `server` is provided and the
271
+ `directory` matches the `directory` that `watch` was given
272
+ [default: current directory]. Additionally, if there is a `playground` key for
273
+ that server, it will be used when using `watch`.
267
274
 
268
275
  #### watchdog
269
276
 
270
- The `watch` command now depends on the
277
+ The `watch` command depends on the
271
278
  [watchdog](https://pypi.org/project/watchdog/) Python package. This allows
272
279
  `watch` to work on the following platforms that [watchdog] supports:
273
280
 
@@ -317,6 +324,28 @@ easy to use and will prompt you as necessary.
317
324
  remove Remove a server from the config file.
318
325
  test Test the URL and API key.
319
326
 
327
+ The `.docassemblecli` config file can store additional configuration for each server:
328
+
329
+ - `directory`: If the current or provided directory matches this then this server will be selected
330
+ - `playground`: Default playground project to use if `directory` matches
331
+ - `startup`: If set to "install", automatically installs package when `watch` starts
332
+
333
+ For example, your `.docassemblecli` file might look like this:
334
+
335
+ ```yaml
336
+ - apiurl: https://da.example.com
337
+ apikey: H3PWMKJOIVAXL4PWUJH3HG7EKPFU5GYT
338
+ name: da.example.com
339
+ playground: testing
340
+ directory: /path/to/docassemble-mypackage
341
+ startup: install
342
+ ```
343
+
344
+ With this configuration:
345
+ - `da watch` in `/path/to/docassemble-mypackage` will automatically use this server
346
+ - It will install to the "testing" playground project
347
+ - It will install the package once when `watch` starts
348
+
320
349
  ## How it works
321
350
 
322
351
  The `install` command is just a simple Python script that creates a ZIP file and
@@ -18,6 +18,7 @@ from packaging import version as packaging_version
18
18
  from watchdog.events import FileSystemEventHandler
19
19
  from watchdog.observers import Observer
20
20
 
21
+
21
22
  global DEFAULT_CONFIG
22
23
  DEFAULT_CONFIG = os.path.join(os.path.expanduser("~"), ".docassemblecli")
23
24
 
@@ -34,6 +35,12 @@ FILE_CHECKSUMS = {}
34
35
  global DEBUG
35
36
  DEBUG = False
36
37
 
38
+ global EXCLUDED_DIRECTORIES
39
+ EXCLUDED_DIRECTORIES = [".git", "__pycache__", ".mypy_cache", ".venv", ".history", "build"]
40
+
41
+ global GITMATCH_COMPILED
42
+ GITMATCH_COMPILED = None
43
+
37
44
  global GITIGNORE
38
45
  GITIGNORE = """\
39
46
  __pycache__/
@@ -87,12 +94,19 @@ share/python-wheels/
87
94
  # click
88
95
  # -----------------------------------------------------------------------------
89
96
 
97
+
90
98
  CONTEXT_SETTINGS = dict(help_option_names=["--help", "-h"])
91
99
 
92
100
 
93
101
  @click.group(context_settings=CONTEXT_SETTINGS)
94
102
  @click.version_option()
95
- @click.option("--color/--no-color", "-C/-N", default=None, show_default=True, help="Overrides color auto-detection in interactive terminals.")
103
+ @click.option(
104
+ "--color/--no-color",
105
+ "-C/-N",
106
+ default=None,
107
+ show_default=True,
108
+ help="Overrides color auto-detection in interactive terminals.",
109
+ )
96
110
  @click.option("--debug/--no-debug", default=False, hidden=True)
97
111
  def cli(color, debug):
98
112
  """
@@ -113,34 +127,76 @@ def config():
113
127
 
114
128
 
115
129
  def common_params_for_api(func):
116
- @click.option("--api", "-a", type=(APIURLType(), str), default=(None, None), help="URL of the docassemble server and API key of the user (admin or developer)")
130
+ @click.option(
131
+ "--api",
132
+ "-a",
133
+ type=(APIURLType(), str),
134
+ default=(None, None),
135
+ help="URL of the docassemble server and API key of the user (admin or developer)",
136
+ )
117
137
  @click.option("--server", "-s", metavar="SERVER", default="", help="Specify a server from the config file")
118
138
  @wraps(func)
119
139
  def wrapper(*args, **kwargs):
120
140
  return func(*args, **kwargs)
141
+
121
142
  return wrapper
122
143
 
123
144
 
124
145
  def common_params_for_config(func):
125
- @click.option("--config", "-c", default=DEFAULT_CONFIG, type=click.Path(), callback=validate_and_load_or_create_config, show_default=True, help="Specify the config file to use")
146
+ @click.option(
147
+ "--config",
148
+ "-c",
149
+ default=DEFAULT_CONFIG,
150
+ type=click.Path(),
151
+ callback=validate_and_load_or_create_config,
152
+ show_default=True,
153
+ help="Specify the config file to use",
154
+ )
126
155
  @wraps(func)
127
156
  def wrapper(*args, **kwargs):
128
157
  return func(*args, **kwargs)
158
+
129
159
  return wrapper
130
160
 
131
161
 
132
162
  def common_params_for_installation(func):
133
- @click.option("--directory", "-d", default=os.getcwd(), type=click.Path(), callback=validate_package_directory, help="Specify package directory [default: current directory]")
134
- @click.option("--config", "-c", is_flag=False, flag_value="", default=DEFAULT_CONFIG, type=click.Path(), callback=validate_and_load_or_create_config, show_default=True, help="Specify the config file to use or leave it blank to skip using any config file")
135
- @click.option("--playground", "-p", metavar="(PROJECT)", is_flag=False, flag_value="default", help="Install into the default Playground or into the specified Playground project.")
163
+ @click.option(
164
+ "--directory",
165
+ "-d",
166
+ default=os.getcwd(),
167
+ type=click.Path(),
168
+ callback=validate_package_directory,
169
+ help="Specify package directory [default: current directory]",
170
+ )
171
+ @click.option(
172
+ "--config",
173
+ "-c",
174
+ is_flag=False,
175
+ flag_value="",
176
+ default=DEFAULT_CONFIG,
177
+ type=click.Path(),
178
+ callback=validate_and_load_or_create_config,
179
+ show_default=True,
180
+ help="Specify the config file to use or leave it blank to skip using any config file",
181
+ )
182
+ @click.option(
183
+ "--playground",
184
+ "-p",
185
+ metavar="(PROJECT)",
186
+ is_flag=False,
187
+ flag_value="default",
188
+ help="Install into the default Playground or into the specified Playground project.",
189
+ )
136
190
  @wraps(func)
137
191
  def wrapper(*args, **kwargs):
138
192
  return func(*args, **kwargs)
193
+
139
194
  return wrapper
140
195
 
141
196
 
142
197
  class APIURLType(click.ParamType):
143
198
  name = "url"
199
+
144
200
  def convert(self, value, param, ctx):
145
201
  parsed_url = urlparse(value)
146
202
  if all([re.search(r"""^https?://[^\s]+$""", value), parsed_url.scheme, parsed_url.netloc]):
@@ -154,7 +210,9 @@ def validate_package_directory(ctx, param, directory: str) -> str:
154
210
  if not os.path.exists(directory):
155
211
  raise click.BadParameter(f"""Directory "{directory}" does not exist.""")
156
212
  if not os.path.isfile(os.path.join(directory, "setup.py")):
157
- raise click.BadParameter(f"""Directory "{directory}" does not contain a setup.py file, so it is not the directory of a valid Python package.""")
213
+ raise click.BadParameter(
214
+ f"""Directory "{directory}" does not contain a setup.py file, so it is not the directory of a valid Python package."""
215
+ )
158
216
  else:
159
217
  return directory
160
218
 
@@ -185,6 +243,7 @@ def validate_and_load_or_create_config(ctx, param, config: str) -> tuple[str, li
185
243
  # utility functions
186
244
  # -----------------------------------------------------------------------------
187
245
 
246
+
188
247
  def name_from_url(url: str) -> str:
189
248
  if not url:
190
249
  return ""
@@ -200,10 +259,18 @@ def display_servers(env: list = None) -> list[str]:
200
259
  servers.append(item.get("name", ""))
201
260
  else:
202
261
  servers.append(item.get("name", "") + " (default)")
262
+ if "playground" in item:
263
+ servers.append(f""" playground: {item["playground"]}""")
264
+ if "directory" in item:
265
+ servers.append(f""" directory: {item["directory"]}""")
266
+ if "startup" in item:
267
+ servers.append(f""" startup: {item["startup"]}""")
203
268
  return servers
204
269
 
205
270
 
206
- def select_server(cfg: str = None, env: list = None, apiurl: str = None, apikey: str = None, server: str = "") -> dict:
271
+ def select_server(
272
+ cfg: str = None, env: list = None, apiurl: str = None, apikey: str = None, server: str = "", **kwargs
273
+ ) -> dict:
207
274
  if apiurl and apikey:
208
275
  return add_server_to_env(cfg=cfg, env=env, apiurl=apiurl, apikey=apikey)[-1]
209
276
  if isinstance(env, list):
@@ -216,6 +283,10 @@ def select_server(cfg: str = None, env: list = None, apiurl: str = None, apikey:
216
283
  return item
217
284
  raise click.BadParameter(f"""Server "{server}" was not found.""", param_hint="--server")
218
285
  if len(env) > 0:
286
+ if "directory" in kwargs:
287
+ for item in env:
288
+ if item.get("directory", None) == kwargs["directory"]:
289
+ return item
219
290
  return env[0]
220
291
  if "DOCASSEMBLEAPIURL" in os.environ and "DOCASSEMBLEAPIKEY" in os.environ:
221
292
  apiurl: str = os.environ["DOCASSEMBLEAPIURL"]
@@ -224,7 +295,9 @@ def select_server(cfg: str = None, env: list = None, apiurl: str = None, apikey:
224
295
  return add_server_to_env(cfg, env)[0]
225
296
 
226
297
 
227
- def add_or_update_env(env: list = None, apiurl: str = "", apikey: str = "") -> list:
298
+ def add_or_update_env(
299
+ env: list = None, apiurl: str = "", apikey: str = "", directory: str = "", playground: str = ""
300
+ ) -> list:
228
301
  if not env:
229
302
  env: list = []
230
303
  apiname: str = name_from_url(apiurl)
@@ -233,11 +306,20 @@ def add_or_update_env(env: list = None, apiurl: str = "", apikey: str = "") -> l
233
306
  if item.get("name", None) == apiname:
234
307
  item["apiurl"] = apiurl
235
308
  item["apikey"] = apikey
309
+ if directory:
310
+ item["directory"] = directory
311
+ if playground:
312
+ item["playground"] = playground
236
313
  found = True
237
314
  click.echo(f"""Server "{apiname}" was found and updated.""")
238
315
  break
239
316
  if not found:
240
- env.append({"apiurl": apiurl, "apikey": apikey, "name": apiname})
317
+ new_server = {"apiurl": apiurl, "apikey": apikey, "name": apiname}
318
+ if directory:
319
+ new_server["directory"] = directory
320
+ if playground:
321
+ new_server["playground"] = playground
322
+ env.append(new_server)
241
323
  return env
242
324
 
243
325
 
@@ -256,7 +338,11 @@ def prompt_for_api(retry: str = False, previous_url: str = None, previous_key: s
256
338
  if retry:
257
339
  if not click.confirm("Do you want to try another URL and API key?", default=True):
258
340
  raise click.Abort()
259
- apiurl = click.prompt("""Base URL of your docassemble server (e.g., https://da.example.com)""", type=APIURLType(), default=previous_url)
341
+ apiurl = click.prompt(
342
+ """Base URL of your docassemble server (e.g., https://da.example.com)""",
343
+ type=APIURLType(),
344
+ default=previous_url,
345
+ )
260
346
  apikey = click.prompt(f"""API key of admin or developer user on {apiurl}""", default=previous_key).strip()
261
347
  return apiurl, apikey
262
348
 
@@ -267,9 +353,13 @@ def test_apiurl_apikey(apiurl: str, apikey: str) -> bool:
267
353
  api_test = requests.get(apiurl + "/api/package", headers={"X-API-Key": apikey})
268
354
  if api_test.status_code != 200:
269
355
  if api_test.status_code == 403:
270
- click.secho(f"""\nThe API KEY is invalid. ({api_test.status_code} {api_test.text.strip()})\n""", fg="red")
356
+ click.secho(
357
+ f"""\nThe API KEY is invalid. ({api_test.status_code} {api_test.text.strip()})\n""", fg="red"
358
+ )
271
359
  else:
272
- click.secho(f"""\nThe API URL or KEY is invalid. ({api_test.status_code} {api_test.text.strip()})\n""", fg="red")
360
+ click.secho(
361
+ f"""\nThe API URL or KEY is invalid. ({api_test.status_code} {api_test.text.strip()})\n""", fg="red"
362
+ )
273
363
  return False
274
364
  except Exception as err:
275
365
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
@@ -279,26 +369,26 @@ def test_apiurl_apikey(apiurl: str, apikey: str) -> bool:
279
369
  return True
280
370
 
281
371
 
282
- def add_server_to_env(cfg: str = None, env: list = None, apiurl: str = None, apikey: str = None):
372
+ def add_server_to_env(
373
+ cfg: str = None,
374
+ env: list = None,
375
+ apiurl: str = None,
376
+ apikey: str = None,
377
+ directory: str = None,
378
+ playground: str = None,
379
+ ):
283
380
  if not apiurl or not apikey:
284
381
  apiurl, apikey = prompt_for_api()
285
382
  while not test_apiurl_apikey(apiurl=apiurl, apikey=apikey):
286
383
  apiurl, apikey = prompt_for_api(retry=True, previous_url=apiurl, previous_key=apikey)
287
- env = add_or_update_env(env=env, apiurl=apiurl, apikey=apikey)
384
+ env = add_or_update_env(env=env, apiurl=apiurl, apikey=apikey, directory=directory, playground=playground)
288
385
  if cfg:
289
386
  if save_config(cfg, env):
290
387
  click.echo(f"""Configuration saved: {cfg}""")
291
388
  return env
292
389
 
293
390
 
294
- def select_env(cfg: str = None, env: list = None, apiurl: str = None, apikey: str = None, server: str = None) -> dict:
295
- if apiurl and apikey:
296
- return add_server_to_env(cfg=cfg, env=env, apiurl=apiurl, apikey=apikey)[-1]
297
- else:
298
- return select_server(cfg=cfg, env=env, server=server)
299
-
300
-
301
- def wait_for_server(playground:bool, task_id: str, apikey: str, apiurl: str, server_version_da: str = "0"):
391
+ def wait_for_server(playground: bool, task_id: str, apikey: str, apiurl: str, server_version_da: str = "0"):
302
392
  click.secho("Waiting for package to install...", fg="cyan")
303
393
  tries = 0
304
394
  before_wait_for_server = time.time()
@@ -312,7 +402,7 @@ def wait_for_server(playground:bool, task_id: str, apikey: str, apiurl: str, ser
312
402
  except requests.exceptions.RequestException:
313
403
  pass
314
404
  if r.status_code != 200:
315
- return("package_update_status returned " + str(r.status_code) + ": " + r.text)
405
+ return "package_update_status returned " + str(r.status_code) + ": " + r.text
316
406
  info = r.json()
317
407
  if info["status"] == "completed" or info["status"] == "unknown":
318
408
  break
@@ -325,7 +415,10 @@ def wait_for_server(playground:bool, task_id: str, apikey: str, apiurl: str, ser
325
415
  success = True
326
416
  elif info.get("ok", False):
327
417
  success = True
328
- if not (server_version_da == "norestart" or packaging_version.parse(server_version_da) >= packaging_version.parse("1.5.3")):
418
+ if not (
419
+ server_version_da == "norestart"
420
+ or packaging_version.parse(server_version_da) >= packaging_version.parse("1.5.3")
421
+ ):
329
422
  if DEBUG:
330
423
  click.echo(f"""Package install duration: {(after_wait_for_server - before_wait_for_server):.2f}s""")
331
424
  click.echo("""Manually waiting for background processes.""")
@@ -345,11 +438,19 @@ def wait_for_server(playground:bool, task_id: str, apikey: str, apiurl: str, ser
345
438
  # package_installer
346
439
  # -----------------------------------------------------------------------------
347
440
 
441
+
348
442
  def package_installer(directory, apiurl, apikey, playground, restart):
349
443
  archive = tempfile.NamedTemporaryFile(suffix=".zip")
350
444
  zf = zipfile.ZipFile(archive, compression=zipfile.ZIP_DEFLATED, mode="w")
351
445
  try:
352
- ignore_process = subprocess.run(["git", "ls-files", "-i", "--directory", "-o", "--exclude-standard"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, cwd=directory, check=False)
446
+ ignore_process = subprocess.run(
447
+ ["git", "ls-files", "-i", "--directory", "-o", "--exclude-standard"],
448
+ stdout=subprocess.PIPE,
449
+ stderr=subprocess.PIPE,
450
+ universal_newlines=True,
451
+ cwd=directory,
452
+ check=False,
453
+ )
353
454
  ignore_process.check_returncode()
354
455
  raw_ignore = ignore_process.stdout.splitlines()
355
456
  except Exception:
@@ -361,7 +462,13 @@ def package_installer(directory, apiurl, apikey, playground, restart):
361
462
  dependencies = {}
362
463
  for root, dirs, files in os.walk(directory, topdown=True):
363
464
  adjusted_root = os.sep.join(root.split(os.sep)[1:])
364
- dirs[:] = [d for d in dirs if d not in [".git", "__pycache__", ".mypy_cache", ".venv", ".history", "build"] and not d.endswith(".egg-info") and os.path.join(adjusted_root, d) not in to_ignore]
465
+ dirs[:] = [
466
+ d
467
+ for d in dirs
468
+ if d not in EXCLUDED_DIRECTORIES
469
+ and not d.endswith(".egg-info")
470
+ and os.path.join(adjusted_root, d) not in to_ignore
471
+ ]
365
472
  if root_directory is None and ("setup.py" in files or "setup.cfg" in files):
366
473
  root_directory = root
367
474
  if "setup.py" in files:
@@ -374,24 +481,48 @@ def package_installer(directory, apiurl, apikey, playground, restart):
374
481
  if m:
375
482
  for package_text in m.group(1).split(","):
376
483
  package_name = package_text.strip()
377
- if len(package_name) >= 3 and package_name[0] == package_name[-1] and package_name[0] in (""", """):
484
+ if (
485
+ len(package_name) >= 3
486
+ and package_name[0] == package_name[-1]
487
+ and package_name[0] in (""", """)
488
+ ):
378
489
  package_name = package_name[1:-1]
379
490
  mm = re.search(r"""(.*)(<=|>=|==|<|>)(.*)""", package_name)
380
491
  if mm:
381
- dependencies[mm.group(1).strip()] = {"installed": False, "operator": mm.group(2), "version": mm.group(3).strip()}
492
+ dependencies[mm.group(1).strip()] = {
493
+ "installed": False,
494
+ "operator": mm.group(2),
495
+ "version": mm.group(3).strip(),
496
+ }
382
497
  else:
383
498
  dependencies[package_name] = {"installed": False, "operator": None, "version": None}
384
499
  for the_file in files:
385
- if the_file.endswith("~") or the_file.endswith(".pyc") or the_file.endswith(".swp") or the_file.startswith("#") or the_file.startswith(".#") or (the_file == ".gitignore" and root_directory == root) or os.path.join(adjusted_root, the_file) in to_ignore:
500
+ if (
501
+ the_file.endswith("~")
502
+ or the_file.endswith(".pyc")
503
+ or the_file.endswith(".swp")
504
+ or the_file.startswith("#")
505
+ or the_file.startswith(".#")
506
+ or (the_file == ".gitignore" and root_directory == root)
507
+ or os.path.join(adjusted_root, the_file) in to_ignore
508
+ ):
386
509
  continue
387
- if not has_python_files and the_file.endswith(".py") and not (the_file == "setup.py" and root == root_directory) and the_file != "__init__.py":
510
+ if (
511
+ not has_python_files
512
+ and the_file.endswith(".py")
513
+ and not (the_file == "setup.py" and root == root_directory)
514
+ and the_file != "__init__.py"
515
+ ):
388
516
  has_python_files = True
389
- zf.write(os.path.join(root, the_file), os.path.relpath(os.path.join(root, the_file), os.path.join(directory, "..")))
517
+ zf.write(
518
+ os.path.join(root, the_file),
519
+ os.path.relpath(os.path.join(root, the_file), os.path.join(directory, "..")),
520
+ )
390
521
  zf.close()
391
522
  archive.seek(0)
392
523
  if restart == "no":
393
524
  should_restart = False
394
- elif restart =="yes" or has_python_files:
525
+ elif restart == "yes" or has_python_files:
395
526
  should_restart = True
396
527
  elif len(dependencies) > 0 or this_package_name:
397
528
  try:
@@ -400,7 +531,7 @@ def package_installer(directory, apiurl, apikey, playground, restart):
400
531
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
401
532
  raise click.ClickException(f"""{err}\n""")
402
533
  if r.status_code != 200:
403
- return("/api/package returned " + str(r.status_code) + ": " + r.text)
534
+ return "/api/package returned " + str(r.status_code) + ": " + r.text
404
535
  installed_packages = r.json()
405
536
  already_installed = False
406
537
  for package_info in installed_packages:
@@ -410,20 +541,33 @@ def package_installer(directory, apiurl, apikey, playground, restart):
410
541
  condition = True
411
542
  if dependency_info["operator"]:
412
543
  if dependency_info["operator"] == "==":
413
- condition = packaging_version.parse(package_info["version"]) == packaging_version.parse(dependency_info["version"])
544
+ condition = packaging_version.parse(package_info["version"]) == packaging_version.parse(
545
+ dependency_info["version"]
546
+ )
414
547
  elif dependency_info["operator"] == "<=":
415
- condition = packaging_version.parse(package_info["version"]) <= packaging_version.parse(dependency_info["version"])
548
+ condition = packaging_version.parse(package_info["version"]) <= packaging_version.parse(
549
+ dependency_info["version"]
550
+ )
416
551
  elif dependency_info["operator"] == ">=":
417
- condition = packaging_version.parse(package_info["version"]) >= packaging_version.parse(dependency_info["version"])
552
+ condition = packaging_version.parse(package_info["version"]) >= packaging_version.parse(
553
+ dependency_info["version"]
554
+ )
418
555
  elif dependency_info["operator"] == "<":
419
- condition = packaging_version.parse(package_info["version"]) < packaging_version.parse(dependency_info["version"])
556
+ condition = packaging_version.parse(package_info["version"]) < packaging_version.parse(
557
+ dependency_info["version"]
558
+ )
420
559
  elif dependency_info["operator"] == ">":
421
- condition = packaging_version.parse(package_info["version"]) > packaging_version.parse(dependency_info["version"])
560
+ condition = packaging_version.parse(package_info["version"]) > packaging_version.parse(
561
+ dependency_info["version"]
562
+ )
422
563
  if condition:
423
564
  dependency_info["installed"] = True
424
565
  if this_package_name and this_package_name in (package_info["name"], package_info["alt_name"]):
425
566
  already_installed = True
426
- should_restart = bool((not already_installed and len(dependencies) > 0) or not all(item["installed"] for item in dependencies.values()))
567
+ should_restart = bool(
568
+ (not already_installed and len(dependencies) > 0)
569
+ or not all(item["installed"] for item in dependencies.values())
570
+ )
427
571
  else:
428
572
  should_restart = True
429
573
  data = {}
@@ -458,12 +602,20 @@ def package_installer(directory, apiurl, apikey, playground, restart):
458
602
  try:
459
603
  requests.post(project_endpoint, data={"project": playground}, headers={"X-API-Key": apikey})
460
604
  except Exception:
461
- return("create project POST returned " + project_list.text)
605
+ return "create project POST returned " + project_list.text
462
606
  else:
463
607
  click.echo("\n")
464
- return("playground list of projects GET returned " + str(project_list.status_code) + ": " + project_list.text)
608
+ return (
609
+ "playground list of projects GET returned " + str(project_list.status_code) + ": " + project_list.text
610
+ )
465
611
  try:
466
- r = requests.post(apiurl + "/api/playground_install", data=data, files={"file": archive}, headers={"X-API-Key": apikey}, timeout=600)
612
+ r = requests.post(
613
+ apiurl + "/api/playground_install",
614
+ data=data,
615
+ files={"file": archive},
616
+ headers={"X-API-Key": apikey},
617
+ timeout=600,
618
+ )
467
619
  except Exception as err:
468
620
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
469
621
  raise click.ClickException(f"""{err}\n""")
@@ -473,17 +625,33 @@ def package_installer(directory, apiurl, apikey, playground, restart):
473
625
  except Exception:
474
626
  error_message = ""
475
627
  if "project" not in data or error_message != "Invalid project.":
476
- return("playground_install POST returned " + str(r.status_code) + ": " + r.text)
628
+ return "playground_install POST returned " + str(r.status_code) + ": " + r.text
477
629
  try:
478
- r = requests.post(apiurl + "/api/playground/project", data={"project": data["project"]}, headers={"X-API-Key": apikey}, timeout=600)
630
+ r = requests.post(
631
+ apiurl + "/api/playground/project",
632
+ data={"project": data["project"]},
633
+ headers={"X-API-Key": apikey},
634
+ timeout=600,
635
+ )
479
636
  except Exception as err:
480
637
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
481
638
  raise click.ClickException(f"""{err}\n""")
482
639
  if r.status_code != 204:
483
- return("needed to create playground project but POST to api/playground/project returned " + str(r.status_code) + ": " + r.text)
640
+ return (
641
+ "needed to create playground project but POST to api/playground/project returned "
642
+ + str(r.status_code)
643
+ + ": "
644
+ + r.text
645
+ )
484
646
  archive.seek(0)
485
647
  try:
486
- r = requests.post(apiurl + "/api/playground_install", data=data, files={"file": archive}, headers={"X-API-Key": apikey}, timeout=600)
648
+ r = requests.post(
649
+ apiurl + "/api/playground_install",
650
+ data=data,
651
+ files={"file": archive},
652
+ headers={"X-API-Key": apikey},
653
+ timeout=600,
654
+ )
487
655
  except Exception as err:
488
656
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
489
657
  raise click.ClickException(f"""{err}\n""")
@@ -491,14 +659,20 @@ def package_installer(directory, apiurl, apikey, playground, restart):
491
659
  try:
492
660
  info = r.json()
493
661
  except Exception:
494
- return(r.text)
662
+ return r.text
495
663
  task_id = info["task_id"]
496
- success = wait_for_server(playground=bool(playground), task_id=task_id, apikey=apikey, apiurl=apiurl, server_version_da=server_version_da)
664
+ success = wait_for_server(
665
+ playground=bool(playground),
666
+ task_id=task_id,
667
+ apikey=apikey,
668
+ apiurl=apiurl,
669
+ server_version_da=server_version_da,
670
+ )
497
671
  elif r.status_code == 204:
498
672
  success = True
499
673
  else:
500
674
  click.echo("\n")
501
- return("playground_install POST returned " + str(r.status_code) + ": " + r.text)
675
+ return "playground_install POST returned " + str(r.status_code) + ": " + r.text
502
676
  if success:
503
677
  click.secho(f"""[{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Installed.""", fg="green")
504
678
  else:
@@ -506,15 +680,23 @@ def package_installer(directory, apiurl, apikey, playground, restart):
506
680
  return 1
507
681
  else:
508
682
  try:
509
- r = requests.post(apiurl + "/api/package", data=data, files={"zip": archive}, headers={"X-API-Key": apikey}, timeout=600)
683
+ r = requests.post(
684
+ apiurl + "/api/package", data=data, files={"zip": archive}, headers={"X-API-Key": apikey}, timeout=600
685
+ )
510
686
  except Exception as err:
511
687
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
512
688
  raise click.ClickException(f"""{err}\n""")
513
689
  if r.status_code != 200:
514
- return("package POST returned " + str(r.status_code) + ": " + r.text)
690
+ return "package POST returned " + str(r.status_code) + ": " + r.text
515
691
  info = r.json()
516
692
  task_id = info["task_id"]
517
- if wait_for_server(playground=bool(playground), task_id=task_id, apikey=apikey, apiurl=apiurl, server_version_da=server_version_da):
693
+ if wait_for_server(
694
+ playground=bool(playground),
695
+ task_id=task_id,
696
+ apikey=apikey,
697
+ apiurl=apiurl,
698
+ server_version_da=server_version_da,
699
+ ):
518
700
  click.secho(f"""[{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Installed.""", fg="green")
519
701
  if not should_restart:
520
702
  try:
@@ -523,7 +705,7 @@ def package_installer(directory, apiurl, apikey, playground, restart):
523
705
  click.secho(f"""\n{err.__class__.__name__}""", fg="red")
524
706
  raise click.ClickException(f"""{err}\n""")
525
707
  if r.status_code != 204:
526
- return("clear_cache returned " + str(r.status_code) + ": " + r.text)
708
+ return "clear_cache returned " + str(r.status_code) + ": " + r.text
527
709
  return 0
528
710
 
529
711
 
@@ -531,10 +713,18 @@ def package_installer(directory, apiurl, apikey, playground, restart):
531
713
  # install
532
714
  # =============================================================================
533
715
 
716
+
534
717
  @cli.command(context_settings=CONTEXT_SETTINGS)
535
718
  @common_params_for_api
536
719
  @common_params_for_installation
537
- @click.option("--restart", "-r", type=click.Choice(["yes", "no", "auto"]), default="auto", show_default=True, help="On package install: yes, force a restart | no, do not restart | auto, only restart if the package has any .py files or if there are dependencies to be installed")
720
+ @click.option(
721
+ "--restart",
722
+ "-r",
723
+ type=click.Choice(["yes", "no", "auto"]),
724
+ default="auto",
725
+ show_default=True,
726
+ help="On package install: yes, force a restart | no, do not restart | auto, only restart if the package has any .py files or if there are dependencies to be installed",
727
+ )
538
728
  def install(directory, config, api, server, playground, restart):
539
729
  """
540
730
  Install a docassemble package on a docassemble server.
@@ -548,7 +738,13 @@ def install(directory, config, api, server, playground, restart):
548
738
  else:
549
739
  click.echo(f"""Location: Playground "{playground}" """)
550
740
  click.secho(f"""[{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Installing...""", fg="yellow")
551
- package_installer(directory=directory, apiurl=selected_server["apiurl"], apikey=selected_server["apikey"], playground=playground, restart=restart)
741
+ package_installer(
742
+ directory=directory,
743
+ apiurl=selected_server["apiurl"],
744
+ apikey=selected_server["apikey"],
745
+ playground=playground,
746
+ restart=restart,
747
+ )
552
748
  return 0
553
749
 
554
750
 
@@ -556,37 +752,49 @@ def install(directory, config, api, server, playground, restart):
556
752
  # watchdog & hashlib
557
753
  # -----------------------------------------------------------------------------
558
754
 
559
- def calculate_md5(filepath: str) -> str:
755
+
756
+ def calculate_checksum(filepath: str) -> str:
560
757
  hash_md5 = hashlib.md5()
561
758
  try:
562
759
  with open(filepath, "rb") as f:
563
- for chunk in iter(lambda: f.read(4096), b""):
760
+ while chunk := f.read(4096):
564
761
  hash_md5.update(chunk)
565
- except FileNotFoundError:
762
+ except (FileNotFoundError, PermissionError) as e:
763
+ click.secho(f"""{e} while calculating checksum.""", fg="red")
566
764
  return ""
567
765
  return hash_md5.hexdigest()
568
766
 
569
767
 
570
768
  def scan_directory(directory):
769
+ if DEBUG:
770
+ click.secho("Scanning files...", fg="cyan")
571
771
  global FILE_CHECKSUMS
572
- for root, _, files in os.walk(directory):
772
+ for current_directory, subdirectories, files in os.walk(directory):
773
+ excluded_directories = EXCLUDED_DIRECTORIES
774
+ subdirectories[:] = [d for d in subdirectories if d not in excluded_directories]
573
775
  for file in files:
574
- filepath = os.path.join(root, file)
776
+ filepath = os.path.join(current_directory, file)
575
777
  if not matches_ignore_patterns(path=filepath, directory=directory):
576
- FILE_CHECKSUMS[filepath] = calculate_md5(filepath)
778
+ FILE_CHECKSUMS[filepath] = calculate_checksum(filepath)
779
+ if DEBUG:
780
+ click.secho("Scanning complete.", fg="green")
577
781
 
578
782
 
579
783
  def matches_ignore_patterns(path: str, directory: str) -> bool:
580
- if os.path.exists(gitignore_path := os.path.join(directory, ".gitignore")):
581
- with open(gitignore_path) as file:
582
- ignore_patterns = [line.strip() for line in file]
583
- else:
584
- ignore_patterns = GITIGNORE.split("\n")
585
- ignore_patterns.extend([".git/", ".gitignore"])
586
- gm = gitmatch.compile(ignore_patterns)
784
+ global GITMATCH_COMPILED
785
+ if not GITMATCH_COMPILED:
786
+ if DEBUG:
787
+ click.echo("GITMATCH_COMPILED")
788
+ if os.path.exists(gitignore_path := os.path.join(directory, ".gitignore")):
789
+ with open(gitignore_path) as file:
790
+ ignore_patterns = [line.strip() for line in file]
791
+ else:
792
+ ignore_patterns = GITIGNORE.split("\n")
793
+ ignore_patterns.extend([".git/", ".gitignore"])
794
+ GITMATCH_COMPILED = gitmatch.compile(ignore_patterns)
587
795
  # Convert the absolute path to a relative path for gitmatch to work
588
796
  path = os.path.relpath(path, directory)
589
- return gm.match(path=path)
797
+ return GITMATCH_COMPILED.match(path=path)
590
798
 
591
799
 
592
800
  class WatchHandler(FileSystemEventHandler):
@@ -600,8 +808,10 @@ class WatchHandler(FileSystemEventHandler):
600
808
  return None
601
809
  if event.event_type == "created" or event.event_type == "modified":
602
810
  if not matches_ignore_patterns(path=event.src_path.replace("\\", "/"), directory=self.directory):
603
- new_checksum = calculate_md5(event.src_path)
604
- if event.src_path not in FILE_CHECKSUMS or (new_checksum and FILE_CHECKSUMS[event.src_path] != new_checksum):
811
+ new_checksum = calculate_checksum(event.src_path)
812
+ if event.src_path not in FILE_CHECKSUMS or (
813
+ new_checksum and FILE_CHECKSUMS[event.src_path] != new_checksum
814
+ ):
605
815
  FILE_CHECKSUMS[event.src_path] = new_checksum
606
816
  LAST_MODIFIED["time"] = time.time()
607
817
  LAST_MODIFIED["files"][str(event.src_path)] = True
@@ -613,16 +823,33 @@ class WatchHandler(FileSystemEventHandler):
613
823
  # watch
614
824
  # =============================================================================
615
825
 
826
+
616
827
  @cli.command(context_settings=CONTEXT_SETTINGS)
617
828
  @common_params_for_installation
618
829
  @common_params_for_api
619
- @click.option("--restart", "-r", type=click.Choice(["yes", "no", "auto"]), default="auto", show_default=True, help="On package install: yes, force a restart | no, do not restart | auto, only restart if any .py files were changed")
620
- @click.option("--buffer", "-b", metavar="SECONDS", default=3, show_default=True, help="(On server restart only) Set the buffer (wait time) between a file change event and package installation. If you are experiencing multiple installs back-to-back, try increasing this value.")
830
+ @click.option(
831
+ "--restart",
832
+ "-r",
833
+ type=click.Choice(["yes", "no", "auto"]),
834
+ default="auto",
835
+ show_default=True,
836
+ help="On package install: yes, force a restart | no, do not restart | auto, only restart if any .py files were changed",
837
+ )
838
+ @click.option(
839
+ "--buffer",
840
+ "-b",
841
+ metavar="SECONDS",
842
+ default=3,
843
+ show_default=True,
844
+ help="(On server restart only) Set the buffer (wait time) between a file change event and package installation. If you are experiencing multiple installs back-to-back, try increasing this value.",
845
+ )
621
846
  def watch(directory, config, api, server, playground, restart, buffer):
622
847
  """
623
848
  Watch a package directory and `install` any changes. Press Ctrl + c to exit.
849
+
850
+ If the --directory option is not specified, `watch` will look for a directory entry in the config file. The corresponding server entry will be selected automatically if the "directory" key in the config file matches the directory being watched. If a match is found, the "playground" key in the config file will be used if it exists and if no --playground option was specified.
624
851
  """
625
- selected_server = select_server(*config, *api, server)
852
+ selected_server = select_server(*config, *api, server, directory=directory)
626
853
  restart_param = restart
627
854
  scan_directory(directory)
628
855
  global LAST_MODIFIED
@@ -632,10 +859,30 @@ def watch(directory, config, api, server, playground, restart, buffer):
632
859
  observer.start()
633
860
  click.echo()
634
861
  click.echo(f"""Server: {selected_server["name"]}""")
862
+
635
863
  if not playground:
636
- click.echo("Location: Package")
637
- else:
864
+ if (
865
+ "directory" in selected_server
866
+ and selected_server["directory"] == directory
867
+ and "playground" in selected_server
868
+ ):
869
+ playground = selected_server["playground"]
870
+ else:
871
+ click.echo("Location: Package")
872
+ if playground:
638
873
  click.echo(f"""Location: Playground "{playground}" """)
874
+
875
+ if "startup" in selected_server and selected_server["startup"] == "install":
876
+ click.secho("""Installing on startup.""", fg="cyan")
877
+ package_installer(
878
+ directory=directory,
879
+ apiurl=selected_server["apiurl"],
880
+ apikey=selected_server["apikey"],
881
+ playground=playground,
882
+ restart=restart,
883
+ )
884
+ click.echo("")
885
+
639
886
  click.echo(f"""Watching: {directory}""")
640
887
  click.secho(f"""[{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}] Started""", fg="green")
641
888
  try:
@@ -652,20 +899,27 @@ def watch(directory, config, api, server, playground, restart, buffer):
652
899
  LAST_MODIFIED["time"] = 0
653
900
  LAST_MODIFIED["files"] = {}
654
901
  LAST_MODIFIED["restart"] = False
655
- package_installer(directory=directory, apiurl=selected_server["apiurl"], apikey=selected_server["apikey"], playground=playground, restart=restart)
902
+ package_installer(
903
+ directory=directory,
904
+ apiurl=selected_server["apiurl"],
905
+ apikey=selected_server["apikey"],
906
+ playground=playground,
907
+ restart=restart,
908
+ )
656
909
  time.sleep(1)
657
910
  except Exception as e:
658
911
  click.echo(f"\nException occurred: {e}")
659
912
  finally:
660
913
  observer.stop()
661
914
  observer.join()
662
- return("""\nStopping "docassemblecli3 watch".""")
915
+ return """\nStopping "docassemblecli3 watch"."""
663
916
 
664
917
 
665
918
  # =============================================================================
666
919
  # create
667
920
  # =============================================================================
668
921
 
922
+
669
923
  @cli.command(context_settings=CONTEXT_SETTINGS)
670
924
  @click.option("--package", metavar="PACKAGE", help="Name of the package you want to create")
671
925
  @click.option("--developer-name", metavar="NAME", help="Name of the developer of the package")
@@ -681,10 +935,10 @@ def create(package, developer_name, developer_email, description, url, license,
681
935
  """
682
936
  pkgname = package
683
937
  if not pkgname:
684
- pkgname = click.prompt("Name of the package you want to create (e.g., childsupport)")
938
+ pkgname = click.prompt("Name of the package you want to create (e.g., childsupport)")
685
939
  pkgname = re.sub(r"\s", "", pkgname)
686
940
  if not pkgname:
687
- return("The package name you entered is invalid.")
941
+ return "The package name you entered is invalid."
688
942
  pkgname = re.sub(r"^docassemble[\-\.]", "", pkgname, flags=re.IGNORECASE)
689
943
  if output:
690
944
  packagedir = output
@@ -692,10 +946,10 @@ def create(package, developer_name, developer_email, description, url, license,
692
946
  packagedir = "docassemble-" + pkgname
693
947
  if os.path.exists(packagedir):
694
948
  if not os.path.isdir(packagedir):
695
- return("Cannot create the directory " + packagedir + " because the path already exists.")
949
+ return "Cannot create the directory " + packagedir + " because the path already exists."
696
950
  dir_listing = list(os.listdir(packagedir))
697
951
  if "setup.py" in dir_listing or "setup.cfg" in dir_listing:
698
- return("The directory " + packagedir + " already has a package in it.")
952
+ return "The directory " + packagedir + " already has a package in it."
699
953
  else:
700
954
  os.makedirs(packagedir, exist_ok=True)
701
955
  if not developer_name:
@@ -724,7 +978,12 @@ __import__("pkg_resources").declare_namespace(__name__)
724
978
 
725
979
  """
726
980
  if "MIT" in license:
727
- licensetext = "The MIT License (MIT)\n\nCopyright (c) " + str(datetime.datetime.now().year) + " " + developer_name + """
981
+ licensetext = (
982
+ "The MIT License (MIT)\n\nCopyright (c) "
983
+ + str(datetime.datetime.now().year)
984
+ + " "
985
+ + developer_name
986
+ + """
728
987
 
729
988
  Permission is hereby granted, free of charge, to any person obtaining a copy
730
989
  of this software and associated documentation files (the "Software"), to deal
@@ -744,10 +1003,21 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
744
1003
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
745
1004
  SOFTWARE.
746
1005
  """
1006
+ )
747
1007
  else:
748
1008
  licensetext = license + "\n"
749
1009
 
750
- readme = "# docassemble." + pkgname + "\n\n" + description + "\n\n## Author\n\n" + developer_name + ", " + developer_email + "\n"
1010
+ readme = (
1011
+ "# docassemble."
1012
+ + pkgname
1013
+ + "\n\n"
1014
+ + description
1015
+ + "\n\n## Author\n\n"
1016
+ + developer_name
1017
+ + ", "
1018
+ + developer_email
1019
+ + "\n"
1020
+ )
751
1021
  manifestin = """\
752
1022
  include README.md
753
1023
  """
@@ -802,22 +1072,44 @@ def find_package_data(where=".", package="", exclude=standard_exclude, exclude_d
802
1072
  return out
803
1073
 
804
1074
  """
805
- setuppy += "setup(name=" + repr("docassemble." + pkgname) + """,
806
- version=""" + repr(version) + """,
807
- description=(""" + repr(description) + """),
808
- long_description=""" + repr(readme) + """,
1075
+ setuppy += (
1076
+ "setup(name="
1077
+ + repr("docassemble." + pkgname)
1078
+ + """,
1079
+ version="""
1080
+ + repr(version)
1081
+ + """,
1082
+ description=("""
1083
+ + repr(description)
1084
+ + """),
1085
+ long_description="""
1086
+ + repr(readme)
1087
+ + """,
809
1088
  long_description_content_type="text/markdown",
810
- author=""" + repr(developer_name) + """,
811
- author_email=""" + repr(developer_email) + """,
812
- license=""" + repr(license) + """,
813
- url=""" + repr(package_url) + """,
1089
+ author="""
1090
+ + repr(developer_name)
1091
+ + """,
1092
+ author_email="""
1093
+ + repr(developer_email)
1094
+ + """,
1095
+ license="""
1096
+ + repr(license)
1097
+ + """,
1098
+ url="""
1099
+ + repr(package_url)
1100
+ + """,
814
1101
  packages=find_packages(),
815
1102
  namespace_packages=["docassemble"],
816
1103
  install_requires=[],
817
1104
  zip_safe=False,
818
- package_data=find_package_data(where='docassemble/""" + pkgname + """/', package='docassemble.""" + pkgname + """'),
1105
+ package_data=find_package_data(where='docassemble/"""
1106
+ + pkgname
1107
+ + """/', package='docassemble."""
1108
+ + pkgname
1109
+ + """'),
819
1110
  )
820
1111
  """
1112
+ )
821
1113
  # maindir = os.path.join(packagedir, "docassemble", pkgname)
822
1114
  questionsdir = os.path.join(packagedir, "docassemble", pkgname, "data", "questions")
823
1115
  templatesdir = os.path.join(packagedir, "docassemble", pkgname, "data", "templates")
@@ -831,7 +1123,7 @@ def find_package_data(where=".", package="", exclude=standard_exclude, exclude_d
831
1123
  os.makedirs(staticdir, exist_ok=True)
832
1124
  if not os.path.isdir(sourcesdir):
833
1125
  os.makedirs(sourcesdir, exist_ok=True)
834
- with open(os.path.join(packagedir, '.gitignore'), 'w', encoding='utf-8') as the_file:
1126
+ with open(os.path.join(packagedir, ".gitignore"), "w", encoding="utf-8") as the_file:
835
1127
  the_file.write(GITIGNORE)
836
1128
  with open(os.path.join(packagedir, "README.md"), "w", encoding="utf-8") as the_file:
837
1129
  the_file.write(readme)
@@ -854,18 +1146,18 @@ def find_package_data(where=".", package="", exclude=standard_exclude, exclude_d
854
1146
  # config
855
1147
  # =============================================================================
856
1148
 
1149
+
857
1150
  @config.command(context_settings=CONTEXT_SETTINGS)
858
1151
  @common_params_for_config
859
- @click.option("--api", "-a", type=(APIURLType(), str), default=(None, None), help="URL of the docassemble server and API key of the user (admin or developer)")
860
- def add(config, api):
1152
+ @common_params_for_api
1153
+ @common_params_for_installation
1154
+ def add(config, api, directory, playground):
861
1155
  """
862
1156
  Add a server to the config file.
863
1157
  """
864
1158
  apiurl, apikey = api
865
- if not apiurl or not apikey:
866
- apiurl, apikey = prompt_for_api(previous_url=apiurl, previous_key=apikey)
867
1159
  cfg, env = config
868
- add_server_to_env(cfg=cfg, env=env, apiurl=apiurl, apikey=apikey)
1160
+ add_server_to_env(cfg=cfg, env=env, apiurl=apiurl, apikey=apikey, directory=directory, playground=playground)
869
1161
 
870
1162
 
871
1163
  @config.command(context_settings=CONTEXT_SETTINGS)
@@ -924,7 +1216,9 @@ def new(config):
924
1216
  def server_version(config, api, server):
925
1217
  selected_server = select_server(*config, *api, server)
926
1218
  try:
927
- r = requests.get(selected_server["apiurl"] + "/api/package", headers={"X-API-Key": selected_server["apikey"]}, timeout=600)
1219
+ r = requests.get(
1220
+ selected_server["apiurl"] + "/api/package", headers={"X-API-Key": selected_server["apikey"]}, timeout=600
1221
+ )
928
1222
  if DEBUG:
929
1223
  click.echo(type(r.status_code))
930
1224
  click.echo(r.status_code)
@@ -953,4 +1247,3 @@ def test(config, api, server):
953
1247
  apikey = selected_server["apikey"]
954
1248
  click.echo(apiurl)
955
1249
  test_apiurl_apikey(apiurl=apiurl, apikey=apikey)
956
-
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "docassemblecli3"
7
- version = "0.2.2"
7
+ version = "0.3.6"
8
8
  authors = [
9
9
  {name = "Jack Adamson", email = "jackadamson@gmail.com"},
10
10
  {name = "Jonathan Pyle", email = "jhpyle@gmail.com"},
@@ -16,7 +16,7 @@ classifiers = [
16
16
  "Programming Language :: Python",
17
17
  ]
18
18
  dynamic = ["description"]
19
- requires-python = ">= 3.8"
19
+ requires-python = ">= 3.10"
20
20
  keywords = ["docassemble"]
21
21
  dependencies = [
22
22
  "click",
@@ -24,7 +24,7 @@ dependencies = [
24
24
  "packaging",
25
25
  "PyYAML",
26
26
  "requests",
27
- "watchdog"
27
+ "watchdog",
28
28
  ]
29
29
 
30
30
  [project.urls]
File without changes