avrae-ls 0.2.1__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
avrae_ls/runtime.py CHANGED
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- import io
4
- import logging
5
- import time
6
3
  import ast
4
+ import io
7
5
  import json
8
- import random
6
+ import logging
9
7
  import math
8
+ import random
9
+ import time
10
+ from types import SimpleNamespace
10
11
  try: # optional dependency
11
12
  import yaml
12
13
  except ImportError: # pragma: no cover - fallback when PyYAML is absent
@@ -16,10 +17,11 @@ from typing import Any, Dict, Set, Callable
16
17
 
17
18
  import d20
18
19
  import draconic
20
+ import httpx
19
21
  from draconic.interpreter import _Break, _Continue, _Return
20
22
 
21
23
  from .context import ContextData, GVarResolver
22
- from .config import VarSources
24
+ from .config import AvraeServiceConfig, VarSources
23
25
  from .api import AliasContextAPI, CharacterAPI, SimpleCombat, SimpleRollResult
24
26
  from . import argparser as avrae_argparser
25
27
  # Minimal stand-in for Avrae's AliasException
@@ -156,8 +158,9 @@ def _default_builtins() -> Dict[str, Any]:
156
158
 
157
159
 
158
160
  class MockExecutor:
159
- def __init__(self):
161
+ def __init__(self, service_config: AvraeServiceConfig | None = None):
160
162
  self._base_builtins = _default_builtins()
163
+ self._service_config = service_config or AvraeServiceConfig()
161
164
 
162
165
  def available_names(self, ctx_data: ContextData) -> Set[str]:
163
166
  builtin_names = set(self._base_builtins.keys())
@@ -220,12 +223,16 @@ class MockExecutor:
220
223
  runtime_character = CharacterAPI(ctx_data.character)
221
224
  return runtime_character # type: ignore[return-value]
222
225
 
226
+ import_cache: dict[str, SimpleNamespace] = {}
227
+ import_stack: list[str] = []
223
228
  builtins = self._build_builtins(
224
229
  ctx_data,
225
230
  resolver,
226
231
  buffer,
227
232
  character_provider=_character_provider,
228
233
  interpreter_ref=interpreter_ref,
234
+ import_cache=import_cache,
235
+ import_stack=import_stack,
229
236
  )
230
237
  interpreter = draconic.DraconicInterpreter(
231
238
  builtins=builtins,
@@ -266,10 +273,18 @@ class MockExecutor:
266
273
  buffer: io.StringIO,
267
274
  character_provider: Callable[[], CharacterAPI] | None = None,
268
275
  interpreter_ref: Dict[str, draconic.DraconicInterpreter | None] | None = None,
276
+ import_cache: Dict[str, SimpleNamespace] | None = None,
277
+ import_stack: list[str] | None = None,
269
278
  ) -> Dict[str, Any]:
270
279
  builtins = dict(self._base_builtins)
271
280
  var_store = ctx_data.vars
272
281
  interpreter_ref = interpreter_ref or {"interpreter": None}
282
+ import_cache = import_cache or {}
283
+ import_stack = import_stack or []
284
+ service_cfg = self._service_config
285
+ verify_cache_sig: str | None = None
286
+ verify_cache_result: Dict[str, Any] | None = None
287
+ verify_cache_error: ValueError | None = None
273
288
 
274
289
  def _print(*args, sep=" ", end="\n"):
275
290
  buffer.write(sep.join(map(str, args)) + end)
@@ -283,22 +298,25 @@ class MockExecutor:
283
298
  return var_store.svars.get(str(name), default)
284
299
 
285
300
  def _get_cvar(name: str, default=None):
286
- return var_store.cvars.get(str(name), default)
301
+ val = var_store.cvars.get(str(name), default)
302
+ return str(val) if val is not None else default
287
303
 
288
304
  def _get_uvar(name: str, default=None):
289
- return var_store.uvars.get(str(name), default)
305
+ val = var_store.uvars.get(str(name), default)
306
+ return str(val) if val is not None else default
290
307
 
291
308
  def _get_uvars():
292
- return dict(var_store.uvars)
309
+ return {k: (str(v) if v is not None else v) for k, v in var_store.uvars.items()}
293
310
 
294
311
  def _set_uvar(name: str, value: Any):
295
- var_store.uvars[str(name)] = value
296
- return value
312
+ str_val = str(value) if value is not None else None
313
+ var_store.uvars[str(name)] = str_val
314
+ return str_val
297
315
 
298
316
  def _set_uvar_nx(name: str, value: Any):
299
317
  key = str(name)
300
318
  if key not in var_store.uvars:
301
- var_store.uvars[key] = value
319
+ var_store.uvars[key] = str(value) if value is not None else None
302
320
  return var_store.uvars[key]
303
321
 
304
322
  def _delete_uvar(name: str):
@@ -307,37 +325,136 @@ class MockExecutor:
307
325
  def _uvar_exists(name: str) -> bool:
308
326
  return str(name) in var_store.uvars
309
327
 
310
- def _exists(name: str) -> bool:
328
+ def _resolve_name(key: str) -> tuple[bool, Any]:
329
+ key = str(key)
311
330
  interp = interpreter_ref.get("interpreter")
312
- if interp is None:
313
- return False
314
- return str(name) in getattr(interp, "_names", {})
331
+ if interp is not None:
332
+ names = getattr(interp, "_names", {})
333
+ if key in names:
334
+ return True, names[key]
335
+
336
+ if key in var_store.cvars:
337
+ return True, var_store.cvars[key]
338
+
339
+ if key in var_store.uvars:
340
+ return True, var_store.uvars[key]
341
+
342
+ return False, None
343
+
344
+ def _exists(name: str) -> bool:
345
+ found, _ = _resolve_name(name)
346
+ return found
315
347
 
316
348
  def _get(name: str, default=None):
317
- interp = interpreter_ref.get("interpreter")
318
- if interp is None:
319
- return default
320
- return getattr(interp, "_names", {}).get(str(name), default)
349
+ found, value = _resolve_name(name)
350
+ return value if found else default
321
351
 
322
352
  def _using(**imports):
323
353
  interp = interpreter_ref.get("interpreter")
324
354
  if interp is None:
325
355
  return None
356
+ user_ns = getattr(interp, "_names", {})
357
+
358
+ def _load_module(addr: str) -> SimpleNamespace:
359
+ if addr in import_cache:
360
+ return import_cache[addr]
361
+ if resolver is None:
362
+ raise ModuleNotFoundError(f"No gvar named {addr!r}")
363
+ mod_contents = resolver.get_local(addr)
364
+ if mod_contents is None:
365
+ raise ModuleNotFoundError(f"No gvar named {addr!r}")
366
+
367
+ old_names = getattr(interp, "_names", {})
368
+ depth_increased = False
369
+ try:
370
+ interp._names = {}
371
+ interp._depth += 1
372
+ depth_increased = True
373
+ if interp._depth > interp._config.max_recursion_depth:
374
+ raise RecursionError("Maximum recursion depth exceeded")
375
+ interp.execute_module(str(mod_contents), module_name=addr)
376
+ mod_ns = SimpleNamespace(**getattr(interp, "_names", {}))
377
+ import_cache[addr] = mod_ns
378
+ return mod_ns
379
+ finally:
380
+ if depth_increased:
381
+ interp._depth -= 1
382
+ interp._names = old_names
383
+
326
384
  for ns, addr in imports.items():
327
- val = None
328
- if resolver:
329
- val = resolver.get_local(addr)
330
- interp._names[str(ns)] = val # type: ignore[attr-defined]
385
+ addr_str = str(addr)
386
+ if addr_str in import_stack:
387
+ circle = " imports\n".join(import_stack)
388
+ raise ImportError(f"Circular import detected!\n{circle} imports\n{addr_str}")
389
+ import_stack.append(addr_str)
390
+ try:
391
+ mod_ns = _load_module(addr_str)
392
+ finally:
393
+ import_stack.pop()
394
+ name = str(ns)
395
+ if name in interp.builtins:
396
+ raise ValueError(f"{name} is already builtin (no shadow assignments).")
397
+ user_ns[name] = mod_ns
398
+
399
+ interp._names = user_ns
331
400
  return None
332
401
 
333
402
  def _signature(data=0):
334
- return f"signature:{int(data)}"
403
+ try:
404
+ data = int(data)
405
+ except ValueError:
406
+ raise TypeError(f"Data {data} could not be converted to integer.")
407
+ return f"mock-signature:{int(data)}"
335
408
 
336
409
  def _verify_signature(sig):
410
+ nonlocal verify_cache_sig, verify_cache_result, verify_cache_error
411
+ sig_str = str(sig)
412
+ if sig_str == verify_cache_sig:
413
+ if verify_cache_error:
414
+ raise verify_cache_error
415
+ return verify_cache_result
416
+
417
+ verify_cache_sig = sig_str
418
+ verify_cache_error = None
419
+ verify_cache_result = None
420
+
421
+ def _call_verify_api(signature: str) -> Dict[str, Any]:
422
+ base_url = (service_cfg.base_url if service_cfg else AvraeServiceConfig.base_url).rstrip("/")
423
+ url = f"{base_url}/bot/signature/verify"
424
+ headers = {"Content-Type": "application/json"}
425
+ if service_cfg and service_cfg.token:
426
+ headers["Authorization"] = str(service_cfg.token)
427
+ try:
428
+ resp = httpx.post(url, json={"signature": signature}, headers=headers, timeout=5)
429
+ except Exception as exc:
430
+ raise ValueError(f"Failed to verify signature: {exc}") from exc
431
+
432
+ try:
433
+ payload = resp.json()
434
+ except Exception as exc:
435
+ raise ValueError("Failed to verify signature: invalid response body") from exc
436
+
437
+ if resp.status_code != 200:
438
+ message = payload.get("error") if isinstance(payload, dict) else None
439
+ raise ValueError(message or f"Failed to verify signature: HTTP {resp.status_code}")
440
+
441
+ if not isinstance(payload, dict):
442
+ raise ValueError("Failed to verify signature: invalid response")
443
+ if payload.get("success") is not True:
444
+ message = payload.get("error")
445
+ raise ValueError(message or "Failed to verify signature: unsuccessful response")
446
+
447
+ data = payload.get("data")
448
+ if not isinstance(data, dict):
449
+ raise ValueError("Failed to verify signature: malformed response")
450
+ return data
451
+
337
452
  try:
338
- return {"signature": str(sig), "valid": True}
339
- except Exception:
340
- return {"signature": None, "valid": False}
453
+ verify_cache_result = _call_verify_api(sig_str)
454
+ except ValueError as exc:
455
+ verify_cache_error = exc
456
+ raise
457
+ return verify_cache_result
341
458
 
342
459
  def _argparse(args, character=None, splitter=avrae_argparser.argsplit, parse_ephem=True):
343
460
  return avrae_argparser.argparse(args, character=character, splitter=splitter, parse_ephem=parse_ephem)
@@ -449,12 +566,20 @@ def _literal_gvars(code: str) -> Set[str]:
449
566
 
450
567
  gvars: set[str] = set()
451
568
  for node in ast.walk(tree):
452
- if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "get_gvar":
453
- if not node.args:
454
- continue
455
- arg = node.args[0]
456
- if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
457
- gvars.add(arg.value)
458
- elif isinstance(arg, ast.Str):
459
- gvars.add(arg.s)
569
+ if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
570
+ if node.func.id == "get_gvar":
571
+ if not node.args:
572
+ continue
573
+ arg = node.args[0]
574
+ if isinstance(arg, ast.Constant) and isinstance(arg.value, str):
575
+ gvars.add(arg.value)
576
+ elif isinstance(arg, ast.Str):
577
+ gvars.add(arg.s)
578
+ elif node.func.id == "using":
579
+ for kw in node.keywords:
580
+ val = kw.value
581
+ if isinstance(val, ast.Constant) and isinstance(val.value, str):
582
+ gvars.add(val.value)
583
+ elif isinstance(val, ast.Str):
584
+ gvars.add(val.s)
460
585
  return gvars
avrae_ls/server.py CHANGED
@@ -68,7 +68,7 @@ class AvraeLanguageServer(LanguageServer):
68
68
 
69
69
  def load_workspace(self, root: Path) -> None:
70
70
  config, warnings = load_config(root)
71
- executor = MockExecutor()
71
+ executor = MockExecutor(config.service)
72
72
  context_builder = ContextBuilder(config)
73
73
  diagnostics = DiagnosticProvider(executor, config.diagnostics)
74
74
  self._state = ServerState(
@@ -45,26 +45,80 @@ def _builtin_sigs() -> Dict[str, FunctionSig]:
45
45
 
46
46
  def _runtime_helper_sigs() -> Dict[str, FunctionSig]:
47
47
  helpers = {
48
- "get_gvar": ["address"],
49
- "get_svar": ["name", "default=None"],
50
- "get_cvar": ["name", "default=None"],
51
- "get_uvar": ["name", "default=None"],
52
- "get_uvars": [],
53
- "set_uvar": ["name", "value"],
54
- "set_uvar_nx": ["name", "value"],
55
- "delete_uvar": ["name"],
56
- "uvar_exists": ["name"],
57
- "exists": ["name"],
58
- "get": ["name", "default=None"],
59
- "using": ["**imports"],
60
- "signature": ["data=0"],
61
- "verify_signature": ["sig"],
62
- "print": ["*values"],
63
- "character": [],
64
- "combat": [],
65
- "argparse": ["args", "character=None", "splitter=argsplit", "parse_ephem=True"],
48
+ "get_gvar": (
49
+ ["address"],
50
+ "Retrieves and returns the value of a global variable (gvar) by address.",
51
+ ),
52
+ "get_svar": (
53
+ ["name", "default=None"],
54
+ "Gets a server variable by name, returning default if it is not present.",
55
+ ),
56
+ "get_cvar": (
57
+ ["name", "default=None"],
58
+ "Gets a character variable by name as a string, returning default if it is not present.",
59
+ ),
60
+ "get_uvar": (
61
+ ["name", "default=None"],
62
+ "Gets a user variable by name as a string, returning default if it is not present.",
63
+ ),
64
+ "get_uvars": (
65
+ [],
66
+ "Returns the mapping of user variables available to the caller (values are strings).",
67
+ ),
68
+ "set_uvar": (
69
+ ["name", "value"],
70
+ "Sets a user variable (stored as a string) and returns the stored value.",
71
+ ),
72
+ "set_uvar_nx": (
73
+ ["name", "value"],
74
+ "Sets a user variable only if it does not already exist; returns the stored string value.",
75
+ ),
76
+ "delete_uvar": (
77
+ ["name"],
78
+ "Deletes a user variable and returns its previous value or None if missing.",
79
+ ),
80
+ "uvar_exists": (
81
+ ["name"],
82
+ "Returns whether a user variable is set.",
83
+ ),
84
+ "exists": (
85
+ ["name"],
86
+ "Returns whether a name is set in the current evaluation context.",
87
+ ),
88
+ "get": (
89
+ ["name", "default=None"],
90
+ "Gets the value of a name using local > cvar > uvar resolution order; returns default if not set.",
91
+ ),
92
+ "using": (
93
+ ["**imports"],
94
+ "Imports one or more gvars as modules into the current namespace with the provided aliases.",
95
+ ),
96
+ "signature": (
97
+ ["data=0"],
98
+ "Generates a signed invocation signature encoding invocation context and optional 5-bit user data.",
99
+ ),
100
+ "verify_signature": (
101
+ ["data"],
102
+ "Verifies a signature generated by signature(); returns context data or raises ValueError when invalid.",
103
+ ),
104
+ "print": (
105
+ ["*values"],
106
+ "Writes values to alias output using the configured separator/end (mirrors Python print).",
107
+ ),
108
+ "character": (
109
+ [],
110
+ "Returns the active character object for this alias, or raises if none is available.",
111
+ ),
112
+ "combat": (
113
+ [],
114
+ "Returns the current combat context if one exists, otherwise None.",
115
+ ),
116
+ "argparse": (
117
+ ["args", "character=None", "splitter=argsplit", "parse_ephem=True"],
118
+ "Parses alias arguments using Avrae's argparse helper.",
119
+ ),
66
120
  }
67
- return {name: FunctionSig(name=name, params=params) for name, params in helpers.items()}
121
+ return {name: FunctionSig(name=name, params=params, doc=doc) for name, (params, doc) in helpers.items()}
68
122
 
69
123
 
70
124
  def _avrae_function_sigs() -> Dict[str, FunctionSig]:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: avrae-ls
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Language server for Avrae draconic aliases
5
5
  Author: 1drturtle
6
6
  Requires-Python: >=3.11
@@ -23,7 +23,7 @@ Language Server Protocol (LSP) implementation targeting Avrae-style draconic ali
23
23
 
24
24
  ## Install (released package)
25
25
 
26
- - CLI/server via `uv tool` (preferred): `uv tool install avrae-ls` then `uv tool run avrae-ls --help` to see stdio/TCP options (same as `python -m avrae_ls`). The VS Code extension uses this invocation by default. The draconic interpreter is vendored, so no Git deps are needed.
26
+ - CLI/server via `uv tool` (preferred): `uv tool install avrae-ls` then `avrae-ls --help` to see stdio/TCP options (same as `python -m avrae_ls`). The VS Code extension uses this invocation by default. The draconic interpreter is vendored, so no Git deps are needed.
27
27
 
28
28
  ## VS Code extension (released)
29
29
 
@@ -37,10 +37,11 @@ Language Server Protocol (LSP) implementation targeting Avrae-style draconic ali
37
37
  - Build everything locally: `make package` (wheel + VSIX in `dist/`).
38
38
  - Run tests/lint: `make check`.
39
39
  - Run via uv tool from source: `uv tool install --from . avrae-ls`.
40
+ - Run diagnostics for a single file (stdout + stderr logs): `avrae-ls --analyze path/to/alias.txt --log-level DEBUG`.
40
41
 
41
42
  ## Releasing (maintainers)
42
43
 
43
44
  1. Bump `pyproject.toml` version.
44
- 2. `make release` (clean, build, `twine check`, upload to PyPI).
45
+ 2. `make release` (clean, build, upload to PyPI).
45
46
  3. Build and attach the VSIX to the GitHub release (`make vsix`).
46
47
  4. Tag and push.
@@ -1,21 +1,21 @@
1
1
  avrae_ls/__init__.py,sha256=BmjrnksGkbG7TPqwbyQvgYj9uei8pFSFpfkRpaGVdJU,63
2
- avrae_ls/__main__.py,sha256=7Osdjqme9unqaGKIHchxVf31egPFlI4_KpBWZvXKpUE,826
2
+ avrae_ls/__main__.py,sha256=ch287lWe11go5xHAE9OkVppixt0vRF401E4zTs2tqQ0,3557
3
3
  avrae_ls/alias_preview.py,sha256=Wy_ZNRq73ojPiLFf6P6fJ7rG7DeFFsFLkLlhFrAjkKI,6117
4
- avrae_ls/api.py,sha256=7kKxFmrvghM4_gv59uRp94DH2MxnVspyjQROT1bY_NE,59430
4
+ avrae_ls/api.py,sha256=sGbwWs5o6M49lyk3qZi6gNdSH_u_BMdghsTlAjpviqQ,64852
5
5
  avrae_ls/argparser.py,sha256=-6RKrXavbSjEyyEeaoz8hRamnB-MEmJt3Cw2smRbmdI,13483
6
6
  avrae_ls/argument_parsing.py,sha256=ezKl65VwuNEDxt6KlYwVQcpy1110UDvf4BqZqgZTcqk,2122
7
- avrae_ls/completions.py,sha256=RAKwDemcg0EUZkc7ncoPO5mKZ8e0cWN2pMt8XKirB9o,18835
7
+ avrae_ls/completions.py,sha256=lMLaEB-8QWGIxdXYo6gUr-ZfXtws2faahAH7hhsbfe8,28183
8
8
  avrae_ls/config.py,sha256=C51wfG6-82uX4dsffqcDLMiXbZZL9JQgfpulAr8eiqs,15267
9
- avrae_ls/context.py,sha256=jAV0GbpqHrD7jvWYNcOeh-hUcnPLsMFX8Emo48Jc59U,5122
9
+ avrae_ls/context.py,sha256=w0uVSR6Pis3q1fF3kSPsqbTirKOE6n3k2XMO8UZI7sk,5719
10
10
  avrae_ls/cvars.py,sha256=0tcVbUHx_CKJ6aou3kEsKX37LRWAjkUWlqqIuSRFlXk,3197
11
- avrae_ls/diagnostics.py,sha256=a0x8CWTQbapno0JOFVw75gYZnPak4uQ0fySr9B8_xj0,15440
11
+ avrae_ls/diagnostics.py,sha256=EAdbckvvWu0pswopaZ6JyVGCHXUl5ZvGBhP2PDVCrVw,16180
12
12
  avrae_ls/dice.py,sha256=DY7V7L-EwAXaCgddgVe9xU1s9lVtiw5Zc2reipNgdTk,874
13
13
  avrae_ls/parser.py,sha256=AuNxNkkfquN9dcyTpmzAZxWcAQ7CV3PQLHUDYLMz_7U,1148
14
- avrae_ls/runtime.py,sha256=HFBCyL0oR4D91z0x4Id8rl6nhPZl3HINHJyBfoVn3Pk,15115
15
- avrae_ls/server.py,sha256=8togXNRC5aM2fX2sss7V-aedkbJBPM7dmawc57_IU7g,13056
16
- avrae_ls/signature_help.py,sha256=S9VnmmvD_CvFQLPuWMlo33eRAhmNi_CsRpWFcoRY6Rk,4823
14
+ avrae_ls/runtime.py,sha256=mxke-uwXQol82Syi6kKi0-GqZ0klRXAeAkZP0qUNW7s,20870
15
+ avrae_ls/server.py,sha256=L3qGTpCDOa64hWFmfWfoKbrDZ3262gmu5wYhHkdLBPY,13070
16
+ avrae_ls/signature_help.py,sha256=JheaEzINV4FO72t5U0AJfL2ZX15y3-gcA6xk3M1jHcY,6980
17
17
  avrae_ls/symbols.py,sha256=8aMalHBDnRsRvhwdbmf0nOazio7G185qw9le45Xb5Mk,4449
18
- avrae_ls-0.2.1.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
18
+ avrae_ls-0.3.0.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
19
19
  draconic/LICENSE,sha256=Fzvu32_DafLKKn2mzxhEdlmrKZzAsigDZ87O7uoVqZI,1067
20
20
  draconic/__init__.py,sha256=YPH420Pcn_nTkfB62hJy_YqC5kpJdzSa78jP8n4z_xY,109
21
21
  draconic/exceptions.py,sha256=siahnHIsumbaUhKBDSrw_DmLZ-0oZks8L5oytPH8hD4,3753
@@ -25,8 +25,8 @@ draconic/string.py,sha256=kGrRc6wNHRq1y5xw8Os-fBhfINDtIY2nBWQWkyLSfQI,2858
25
25
  draconic/types.py,sha256=1Lsr6z8bW5agglGI4hLt_nPtYuZOIf_ueSpPDB4WDrs,13686
26
26
  draconic/utils.py,sha256=D4vJ-txqS2-rlqsEpXAC46_j1sZX4UjY-9zIgElo96k,3122
27
27
  draconic/versions.py,sha256=CUEsgUWjAmjez0432WwiBwZlIzWPIObwZUf8Yld18EE,84
28
- avrae_ls-0.2.1.dist-info/METADATA,sha256=ukIqEUjW7AT9Ry2CeW4U9nFsDUJ4Clwfa-mbFpjv0SQ,2016
29
- avrae_ls-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- avrae_ls-0.2.1.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
31
- avrae_ls-0.2.1.dist-info/top_level.txt,sha256=TL68uzGHmB2R2ID32_s2zocmcNnpMJVQ6_4NBvyo8a4,18
32
- avrae_ls-0.2.1.dist-info/RECORD,,
28
+ avrae_ls-0.3.0.dist-info/METADATA,sha256=wljFfTfBdkF2SBdWjThl9lpz64fTj5XL4ufqgFvtcS4,2107
29
+ avrae_ls-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
+ avrae_ls-0.3.0.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
31
+ avrae_ls-0.3.0.dist-info/top_level.txt,sha256=TL68uzGHmB2R2ID32_s2zocmcNnpMJVQ6_4NBvyo8a4,18
32
+ avrae_ls-0.3.0.dist-info/RECORD,,