@descryy/runtime-adapter-python 0.3.0 → 0.4.0

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,97 +1,48 @@
1
1
  /**
2
- * Python's `LogEnvelope`s. Currently one: **uvicorn** (RT-106).
3
- *
4
- * ## This file exists because an earlier claim of mine was too broad
5
- *
6
- * RT-103 measured Flask, Django and FastAPI all printing
7
- * `Traceback (most recent call last):` at column 0 with CPython's own
8
- * two-space frame indent, because `logging` appends `exc_info` unindented,
9
- * and concluded **"Python needs no envelope at all and never will."**
10
- *
11
- * That is right about *unwrapping* and wrong about *record boundaries*, and
12
- * the two are different jobs behind one interface. Nothing needs stripping
13
- * from a Python traceback line. But `UnwrappedLine.startsRecord` is the
14
- * only mechanism by which a collector can know that a line **ends the
15
- * previous record**, and Python is the language where that matters most:
16
- *
17
- * ```
18
- * Traceback (most recent call last):
19
- * INFO: 127.0.0.1:50002 - "GET /health HTTP/1.1" 200 OK
20
- * File "/app/service.py", line 18, in charge_invoice
21
- * ```
22
- *
23
- * A traceback is written one `write()` per line, so any other thread
24
- * logging to the same stream interleaves between them. Measured on real
25
- * threaded CPython 3.12, three runs of 25 failures: 7, 8 and 7 of them were
26
- * reported as an EXCEPTION whose entire identity was an access-log line for
27
- * a **successful** request — see `TRAILER_PATTERN` in
28
- * `python-frame-parsing.ts` for the full measurement and for the heuristic
29
- * that an un-enveloped stream falls back on.
30
- *
31
- * This envelope makes that case structural rather than guessed. uvicorn
32
- * *states* where a record begins; the trailer pattern can only infer it.
33
- *
34
- * ## Nothing is stripped from a traceback, and that is the whole design
35
- *
36
- * uvicorn formats with `"%(levelprefix)s %(message)s"`, where `levelprefix`
37
- * is `f"{levelname}:".ljust(9)`. That prefix goes on the record's **first**
38
- * line only. The `exc_info` traceback `logging` appends afterwards is
39
- * written through untouched, at column 0, with CPython's own indentation.
40
- *
41
- * Measured against real `uvicorn 0.41.0` / `fastapi 0.141.1` output
42
- * (`test/fixtures/uvicorn-asgi-failure.txt`): `ERROR: Exception in ASGI
43
- * application` is the header, and all 28 traceback lines beneath it are
44
- * continuations returned byte-identical. So `unwrap` is the identity
45
- * function for every line that carries evidence, and the envelope's entire
46
- * contribution is `startsRecord` — the opposite balance to Ruby's, where
2
+ * Python's LogEnvelopes. Currently one: uvicorn (RT-106).
3
+ *
4
+ * RT-103 measured Flask/Django/FastAPI all printing tracebacks at column 0
5
+ * with CPython's own indent (logging appends exc_info unindented) and
6
+ * concluded Python needs no envelope. Right about unwrapping, wrong about
7
+ * record boundaries — different jobs behind one interface.
8
+ * UnwrappedLine.startsRecord is the only way a collector knows a line ends
9
+ * the previous record, and interleaved logging threads make that matter: a
10
+ * traceback is written one write() per line, so another thread's logging
11
+ * can land between them. Measured on real threaded CPython 3.12, three runs
12
+ * of 25 failures: 7, 8, 7 reported as an EXCEPTION whose entire identity was
13
+ * an access-log line for a successful request (see TRAILER_PATTERN in
14
+ * python-frame-parsing.ts for the full measurement). This envelope makes
15
+ * uvicorn's record boundary structural instead of inferred.
16
+ *
17
+ * Nothing is stripped from a traceback: uvicorn's `levelprefix` only prefixes
18
+ * the record's first line; the exc_info traceback logging appends afterwards
19
+ * passes through untouched. Measured against real uvicorn 0.41.0/fastapi
20
+ * 0.141.1 output: all 28 traceback lines are continuations returned
21
+ * byte-identical, so unwrap is identity and the envelope's whole
22
+ * contribution is startsRecord — opposite of Ruby's envelope, where
47
23
  * unwrapping is the load-bearing half.
48
24
  *
49
- * ## Why the level set is closed rather than `\w+`
50
- *
51
- * `^(\w+): +` would match `TypeError: ...` — a real trailer, wrongly
52
- * read as a record boundary, closing a block one line before its identity
53
- * arrives. That is the same class of over-matching this envelope exists to
54
- * fix, introduced from the other side. So the set is exactly the six level
55
- * names `logging` and uvicorn can produce, and a seventh would be a
56
- * measured addition rather than a widened pattern.
57
- *
58
- * ## Two disclosed limits
25
+ * Level set is closed, not `\w+`: `^(\w+): +` would match `TypeError: ...`,
26
+ * a real trailer wrongly read as a record boundary. Set is exactly the six
27
+ * level names logging/uvicorn can produce.
59
28
  *
60
- * **Colour.** uvicorn wraps `levelprefix` in ANSI when its output is a TTY.
61
- * Descry pipes, so the measured stream has none, and this pattern would not
62
- * match a colourised one. Stated rather than handled: adding an optional
63
- * escape-sequence prefix on an unmeasured shape is the guess this file's
64
- * own argument rejects.
65
- *
66
- * **Application loggers are not covered.** uvicorn configures only its own
67
- * `uvicorn`/`uvicorn.error`/`uvicorn.access` loggers. An application's own
68
- * `logging.getLogger(__name__).info(...)` goes through the root config and
69
- * usually carries no prefix at all, so it comes back from `unwrap`
70
- * untouched with `startsRecord: false` — correct, because this envelope has
71
- * no idea whether that line began a record, and saying so would be the
72
- * inference `fields()` exists to refuse.
29
+ * Two disclosed limits: uvicorn wraps levelprefix in ANSI on a TTY (Descry
30
+ * pipes, so unmeasured, unmatched); and application loggers via
31
+ * `logging.getLogger(__name__)` carry no prefix, so they come back
32
+ * untouched with startsRecord: false — correct, since this envelope has no
33
+ * way to know whether such a line began a record.
73
34
  */
74
35
  import { type LogEnvelope, type LogPattern, type LogPatternRegistry } from "@descryy/runtime-backend-observation";
75
36
  /**
76
- * uvicorn's wrapper, expressed as a registry entry rather than as a private
77
- * constant inside one envelope factory.
78
- *
79
- * The regexes and the fields are unchanged — this is the same measurement
80
- * against the same real `uvicorn 0.41.0` output, moved to where a caller can
81
- * name it and compose it with another format. What changed is that
82
- * `PythonRuntimeAdapterOptions.server` is no longer the entire Python
83
- * vocabulary: a process that prints uvicorn's access lines AND formatted
84
- * console output can now be given both, which is what UAT phase 2's Phase 9
85
- * asked for and what a closed union of framework names structurally cannot do.
37
+ * uvicorn's wrapper, as a registry entry rather than a private constant, so a
38
+ * process printing uvicorn's access lines AND formatted console output can
39
+ * be given both — a closed union of framework names couldn't do that.
86
40
  */
87
41
  export declare const UVICORN_LOG_PATTERN: LogPattern;
88
42
  /**
89
- * The patterns a Python process can be declared to use: the framework-agnostic
90
- * built-ins (the bracket-tag consoles — `rich`, and anything else that prints
91
- * `[TAG] message`) plus uvicorn's own.
92
- *
93
- * A fresh registry per call, so a caller registering its own pattern cannot
94
- * change what another caller resolves a name to.
43
+ * The patterns a Python process can be declared to use: bracket-tag consoles
44
+ * (rich, `[TAG] message`) plus uvicorn's own. Fresh registry per call, so
45
+ * one caller's registered pattern can't change another's resolution.
95
46
  */
96
47
  export declare function pythonLogPatternRegistry(): LogPatternRegistry;
97
48
  /** Unchanged in behaviour and in name: exactly `pythonLogPatternRegistry().envelope(["uvicorn"])`, kept because it is the spelling every existing caller and test uses. */
@@ -1 +1 @@
1
- {"version":3,"file":"python-log-envelopes.d.ts","sourceRoot":"","sources":["../src/python-log-envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAEH,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,sCAAsC,CAAC;AAqB9C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,mBAAmB,EAAE,UA4BjC,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,kBAAkB,CAI7D;AAED,2KAA2K;AAC3K,wBAAgB,wBAAwB,IAAI,WAAW,CAEtD"}
1
+ {"version":3,"file":"python-log-envelopes.d.ts","sourceRoot":"","sources":["../src/python-log-envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,sCAAsC,CAAC;AAiB9C;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAqBjC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,kBAAkB,CAI7D;AAED,2KAA2K;AAC3K,wBAAgB,wBAAwB,IAAI,WAAW,CAEtD"}
@@ -1,86 +1,43 @@
1
1
  /**
2
- * Python's `LogEnvelope`s. Currently one: **uvicorn** (RT-106).
3
- *
4
- * ## This file exists because an earlier claim of mine was too broad
5
- *
6
- * RT-103 measured Flask, Django and FastAPI all printing
7
- * `Traceback (most recent call last):` at column 0 with CPython's own
8
- * two-space frame indent, because `logging` appends `exc_info` unindented,
9
- * and concluded **"Python needs no envelope at all and never will."**
10
- *
11
- * That is right about *unwrapping* and wrong about *record boundaries*, and
12
- * the two are different jobs behind one interface. Nothing needs stripping
13
- * from a Python traceback line. But `UnwrappedLine.startsRecord` is the
14
- * only mechanism by which a collector can know that a line **ends the
15
- * previous record**, and Python is the language where that matters most:
16
- *
17
- * ```
18
- * Traceback (most recent call last):
19
- * INFO: 127.0.0.1:50002 - "GET /health HTTP/1.1" 200 OK
20
- * File "/app/service.py", line 18, in charge_invoice
21
- * ```
22
- *
23
- * A traceback is written one `write()` per line, so any other thread
24
- * logging to the same stream interleaves between them. Measured on real
25
- * threaded CPython 3.12, three runs of 25 failures: 7, 8 and 7 of them were
26
- * reported as an EXCEPTION whose entire identity was an access-log line for
27
- * a **successful** request — see `TRAILER_PATTERN` in
28
- * `python-frame-parsing.ts` for the full measurement and for the heuristic
29
- * that an un-enveloped stream falls back on.
30
- *
31
- * This envelope makes that case structural rather than guessed. uvicorn
32
- * *states* where a record begins; the trailer pattern can only infer it.
33
- *
34
- * ## Nothing is stripped from a traceback, and that is the whole design
35
- *
36
- * uvicorn formats with `"%(levelprefix)s %(message)s"`, where `levelprefix`
37
- * is `f"{levelname}:".ljust(9)`. That prefix goes on the record's **first**
38
- * line only. The `exc_info` traceback `logging` appends afterwards is
39
- * written through untouched, at column 0, with CPython's own indentation.
40
- *
41
- * Measured against real `uvicorn 0.41.0` / `fastapi 0.141.1` output
42
- * (`test/fixtures/uvicorn-asgi-failure.txt`): `ERROR: Exception in ASGI
43
- * application` is the header, and all 28 traceback lines beneath it are
44
- * continuations returned byte-identical. So `unwrap` is the identity
45
- * function for every line that carries evidence, and the envelope's entire
46
- * contribution is `startsRecord` — the opposite balance to Ruby's, where
2
+ * Python's LogEnvelopes. Currently one: uvicorn (RT-106).
3
+ *
4
+ * RT-103 measured Flask/Django/FastAPI all printing tracebacks at column 0
5
+ * with CPython's own indent (logging appends exc_info unindented) and
6
+ * concluded Python needs no envelope. Right about unwrapping, wrong about
7
+ * record boundaries — different jobs behind one interface.
8
+ * UnwrappedLine.startsRecord is the only way a collector knows a line ends
9
+ * the previous record, and interleaved logging threads make that matter: a
10
+ * traceback is written one write() per line, so another thread's logging
11
+ * can land between them. Measured on real threaded CPython 3.12, three runs
12
+ * of 25 failures: 7, 8, 7 reported as an EXCEPTION whose entire identity was
13
+ * an access-log line for a successful request (see TRAILER_PATTERN in
14
+ * python-frame-parsing.ts for the full measurement). This envelope makes
15
+ * uvicorn's record boundary structural instead of inferred.
16
+ *
17
+ * Nothing is stripped from a traceback: uvicorn's `levelprefix` only prefixes
18
+ * the record's first line; the exc_info traceback logging appends afterwards
19
+ * passes through untouched. Measured against real uvicorn 0.41.0/fastapi
20
+ * 0.141.1 output: all 28 traceback lines are continuations returned
21
+ * byte-identical, so unwrap is identity and the envelope's whole
22
+ * contribution is startsRecord — opposite of Ruby's envelope, where
47
23
  * unwrapping is the load-bearing half.
48
24
  *
49
- * ## Why the level set is closed rather than `\w+`
50
- *
51
- * `^(\w+): +` would match `TypeError: ...` — a real trailer, wrongly
52
- * read as a record boundary, closing a block one line before its identity
53
- * arrives. That is the same class of over-matching this envelope exists to
54
- * fix, introduced from the other side. So the set is exactly the six level
55
- * names `logging` and uvicorn can produce, and a seventh would be a
56
- * measured addition rather than a widened pattern.
57
- *
58
- * ## Two disclosed limits
59
- *
60
- * **Colour.** uvicorn wraps `levelprefix` in ANSI when its output is a TTY.
61
- * Descry pipes, so the measured stream has none, and this pattern would not
62
- * match a colourised one. Stated rather than handled: adding an optional
63
- * escape-sequence prefix on an unmeasured shape is the guess this file's
64
- * own argument rejects.
25
+ * Level set is closed, not `\w+`: `^(\w+): +` would match `TypeError: ...`,
26
+ * a real trailer wrongly read as a record boundary. Set is exactly the six
27
+ * level names logging/uvicorn can produce.
65
28
  *
66
- * **Application loggers are not covered.** uvicorn configures only its own
67
- * `uvicorn`/`uvicorn.error`/`uvicorn.access` loggers. An application's own
68
- * `logging.getLogger(__name__).info(...)` goes through the root config and
69
- * usually carries no prefix at all, so it comes back from `unwrap`
70
- * untouched with `startsRecord: false` — correct, because this envelope has
71
- * no idea whether that line began a record, and saying so would be the
72
- * inference `fields()` exists to refuse.
29
+ * Two disclosed limits: uvicorn wraps levelprefix in ANSI on a TTY (Descry
30
+ * pipes, so unmeasured, unmatched); and application loggers via
31
+ * `logging.getLogger(__name__)` carry no prefix, so they come back
32
+ * untouched with startsRecord: false — correct, since this envelope has no
33
+ * way to know whether such a line began a record.
73
34
  */
74
35
  import { createLogPatternRegistry, createPatternLogEnvelope, } from "@descryy/runtime-backend-observation";
75
36
  /**
76
37
  * `INFO: 127.0.0.1:39334 - "GET /health HTTP/1.1" 200 OK`
77
- *
78
- * uvicorn's `DefaultFormatter`/`AccessFormatter` both use
79
- * `"%(levelprefix)s %(message)s"` with `levelprefix = f"{levelname}:".ljust(9)`,
80
- * so the separator is between 1 and 5 spaces depending on the level's own
81
- * length — `CRITICAL:` needs no padding, `INFO:` needs four. Anchored at
82
- * column zero: a traceback's frame lines are indented and its trailer is
83
- * not level-shaped, so neither can reach this.
38
+ * uvicorn's formatters use `levelprefix = f"{levelname}:".ljust(9)`, so 1-5
39
+ * spaces depending on level length. Anchored at column zero: traceback frame
40
+ * lines are indented, its trailer isn't level-shaped.
84
41
  */
85
42
  const UVICORN_HEADER = /^(CRITICAL|ERROR|WARNING|INFO|DEBUG|TRACE): +/;
86
43
  /**
@@ -90,16 +47,9 @@ const UVICORN_HEADER = /^(CRITICAL|ERROR|WARNING|INFO|DEBUG|TRACE): +/;
90
47
  */
91
48
  const ACCESS_MESSAGE = /^(?<client>\S+) - "(?<method>[A-Z]+) (?<path>\S+) (?<protocol>\S+)" (?<status>\d{3})\b/;
92
49
  /**
93
- * uvicorn's wrapper, expressed as a registry entry rather than as a private
94
- * constant inside one envelope factory.
95
- *
96
- * The regexes and the fields are unchanged — this is the same measurement
97
- * against the same real `uvicorn 0.41.0` output, moved to where a caller can
98
- * name it and compose it with another format. What changed is that
99
- * `PythonRuntimeAdapterOptions.server` is no longer the entire Python
100
- * vocabulary: a process that prints uvicorn's access lines AND formatted
101
- * console output can now be given both, which is what UAT phase 2's Phase 9
102
- * asked for and what a closed union of framework names structurally cannot do.
50
+ * uvicorn's wrapper, as a registry entry rather than a private constant, so a
51
+ * process printing uvicorn's access lines AND formatted console output can
52
+ * be given both — a closed union of framework names couldn't do that.
103
53
  */
104
54
  export const UVICORN_LOG_PATTERN = {
105
55
  name: "uvicorn",
@@ -108,16 +58,9 @@ export const UVICORN_LOG_PATTERN = {
108
58
  fields(match) {
109
59
  const access = ACCESS_MESSAGE.exec(match.input.slice(match[0].length));
110
60
  return {
111
- // uvicorn's own word, never normalised to a shared vocabulary.
112
- level: match[1] ?? null,
113
- // uvicorn's formatter discards the logger name — `uvicorn.access`
114
- // and `uvicorn.error` are indistinguishable in the printed stream.
115
- // Null because it is genuinely absent, not merely unparsed.
116
- category: null,
117
- // uvicorn has no request-id concept and prints none. Unlike
118
- // Kestrel's `Request id "..."`, there is nothing here to read, and
119
- // deriving one from the client port would be an invention.
120
- requestId: null,
61
+ level: match[1] ?? null, // uvicorn's own word, not normalised
62
+ category: null, // uvicorn's formatter discards the logger name; genuinely absent, not unparsed
63
+ requestId: null, // uvicorn has no request-id concept; deriving one would be invention
121
64
  attrs: access?.groups === undefined
122
65
  ? {}
123
66
  : {
@@ -130,12 +73,9 @@ export const UVICORN_LOG_PATTERN = {
130
73
  },
131
74
  };
132
75
  /**
133
- * The patterns a Python process can be declared to use: the framework-agnostic
134
- * built-ins (the bracket-tag consoles — `rich`, and anything else that prints
135
- * `[TAG] message`) plus uvicorn's own.
136
- *
137
- * A fresh registry per call, so a caller registering its own pattern cannot
138
- * change what another caller resolves a name to.
76
+ * The patterns a Python process can be declared to use: bracket-tag consoles
77
+ * (rich, `[TAG] message`) plus uvicorn's own. Fresh registry per call, so
78
+ * one caller's registered pattern can't change another's resolution.
139
79
  */
140
80
  export function pythonLogPatternRegistry() {
141
81
  const registry = createLogPatternRegistry();
@@ -1 +1 @@
1
- {"version":3,"file":"python-log-envelopes.js","sourceRoot":"","sources":["../src/python-log-envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAEH,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GAKzB,MAAM,sCAAsC,CAAC;AAE9C;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,+CAA+C,CAAC;AAEvE;;;;GAIG;AACH,MAAM,cAAc,GAAG,wFAAwF,CAAC;AAEhH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,cAAc;IACtB,kBAAkB,EAAE,IAAI;IACxB,MAAM,CAAC,KAAsB;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACvE,OAAO;YACL,+DAA+D;YAC/D,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI;YACvB,kEAAkE;YAClE,mEAAmE;YACnE,4DAA4D;YAC5D,QAAQ,EAAE,IAAI;YACd,4DAA4D;YAC5D,mEAAmE;YACnE,2DAA2D;YAC3D,SAAS,EAAE,IAAI;YACf,KAAK,EACH,MAAM,EAAE,MAAM,KAAK,SAAS;gBAC1B,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;oBACrC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;oBACrC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;oBACjC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;iBACtC;SACR,CAAC;IACJ,CAAC;CACF,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB;IACtC,MAAM,QAAQ,GAAG,wBAAwB,EAAE,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACvC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,2KAA2K;AAC3K,MAAM,UAAU,wBAAwB;IACtC,OAAO,wBAAwB,CAAC,SAAS,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;AACpE,CAAC"}
1
+ {"version":3,"file":"python-log-envelopes.js","sourceRoot":"","sources":["../src/python-log-envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EACL,wBAAwB,EACxB,wBAAwB,GAKzB,MAAM,sCAAsC,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,cAAc,GAAG,+CAA+C,CAAC;AAEvE;;;;GAIG;AACH,MAAM,cAAc,GAAG,wFAAwF,CAAC;AAEhH;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAe;IAC7C,IAAI,EAAE,SAAS;IACf,MAAM,EAAE,cAAc;IACtB,kBAAkB,EAAE,IAAI;IACxB,MAAM,CAAC,KAAsB;QAC3B,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACvE,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,qCAAqC;YAC9D,QAAQ,EAAE,IAAI,EAAE,+EAA+E;YAC/F,SAAS,EAAE,IAAI,EAAE,qEAAqE;YACtF,KAAK,EACH,MAAM,EAAE,MAAM,KAAK,SAAS;gBAC1B,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC;oBACE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;oBACrC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;oBACrC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE;oBACjC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;iBACtC;SACR,CAAC;IACJ,CAAC;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,wBAAwB;IACtC,MAAM,QAAQ,GAAG,wBAAwB,EAAE,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACvC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,2KAA2K;AAC3K,MAAM,UAAU,wBAAwB;IACtC,OAAO,wBAAwB,CAAC,SAAS,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC;AACpE,CAAC"}
@@ -1,66 +1,41 @@
1
1
  /**
2
- * The concrete `RuntimeAdapter` for Python — the second language, and the
3
- * first one to test whether the seam `@descryy/runtime-backend-observation`
4
- * defines is genuinely language-neutral or merely V8-shaped.
5
- *
6
- * It was not, in two places, and both were fixed in the seam rather than
7
- * worked around here:
8
- *
9
- * - `LineClassifier` had no notion of a line that ends a block and belongs
10
- * to it, because a V8 header names its exception up front. A CPython
11
- * traceback names it last. `isTrailer` is that concept, added
12
- * language-neutrally.
13
- * - `StackTrace.frames` had no documented order, because with one language
14
- * nothing ever had to convert. Python prints the opposite of V8. The
15
- * contract now states most-recent-first and this adapter reverses.
16
- *
17
- * Both are recorded because they are the general lesson of adding a second
18
- * anything: a seam with one implementation is a seam in name only, and the
19
- * defects it hides are silent ones — a truncated traceback and a
20
- * backwards frame list both look like well-formed output.
2
+ * The concrete RuntimeAdapter for Python — the second language, first to
3
+ * test whether @descryy/runtime-backend-observation's seam is genuinely
4
+ * language-neutral or merely V8-shaped. It wasn't, in two places, fixed in
5
+ * the seam rather than worked around here:
6
+ * - LineClassifier had no `isTrailer` concept (a V8 header names its
7
+ * exception up front; CPython names it last) — added language-neutrally.
8
+ * - StackTrace.frames had no documented order (Python prints the opposite
9
+ * of V8) — contract now states most-recent-first, this adapter reverses.
21
10
  */
22
11
  import type { RuntimeAdapter } from "@descryy/runtime-backend-observation";
23
12
  export interface PythonRuntimeAdapterOptions {
24
13
  /** Attributed on every emitted Evidence's `service` field (plan §16.7). Omit when not applicable. */
25
14
  readonly service?: string;
26
15
  /**
27
- * The server whose logging layer wraps this process's output (§16).
16
+ * The server whose logging layer wraps this process's output.
17
+ * Declared, never sniffed — uvicorn's `LEVEL:` format is `logging`'s own
18
+ * default shape, so sniffing it would enable record-boundary flushing for
19
+ * any process that ever printed `INFO: ...`, and a wrong boundary
20
+ * truncates an exception block. Only uvicorn, because only it was
21
+ * measured (RT-106) — Gunicorn's and Django runserver's formats differ
22
+ * and get no envelope until measured.
28
23
  *
29
- * **Declared, never sniffed** — the same rule ASP.NET's option follows,
30
- * and for a sharper reason here. uvicorn's format is `LEVEL:` plus
31
- * padding, which is `logging`'s own default shape; detecting it from a
32
- * line that looks like one would enable record-boundary flushing for any
33
- * Python process that ever printed `INFO: ...`, and the cost of a wrong
34
- * boundary is a truncated exception block. A caller that knows it
35
- * launched uvicorn says so.
36
- *
37
- * Only uvicorn, and only because it was measured (RT-106). Gunicorn's
38
- * default `[%(asctime)s] [%(process)d] [%(levelname)s]` and Django's
39
- * `runserver` output are different formats and get no envelope until one
40
- * of them is run and read.
41
- *
42
- * **Superseded in reach, not in rule, by `logPatterns`.** This option is
43
- * exactly `logPatterns: ["uvicorn"]` and is kept because it is the spelling
44
- * every existing caller uses. Declaring both is refused rather than merged.
24
+ * Superseded in reach, not in rule, by `logPatterns` (exactly
25
+ * `logPatterns: ["uvicorn"]`); kept for existing callers. Declaring both is refused, not merged.
45
26
  */
46
27
  readonly server?: "uvicorn";
47
28
  /**
48
- * Log patterns this process's output is declared to use, by name, from
49
- * `pythonLogPatternRegistry()` — composed into one envelope in the order
50
- * given, first match per line winning.
29
+ * Log patterns this process declares, by name from pythonLogPatternRegistry(),
30
+ * composed into one envelope, first match per line winning.
51
31
  *
52
- * **This is UAT phase 2's Phase 9.** `server` was the entire Python
53
- * vocabulary and it named one framework, so a process printing formatted
54
- * console output — `[POSTGRES] Creating and opening a Postgres connection
55
- * pool`, through `rich.console.Console().print()` — had no envelope that
56
- * could see it, and those were the most operationally interesting lines of
57
- * the run that found this. `["uvicorn", "bracket-tag-console"]` covers both
58
- * halves of exactly that process.
32
+ * `server` named only one framework, so a process also printing formatted
33
+ * console output (rich.console.Console().print()) had no envelope for it —
34
+ * `["uvicorn", "bracket-tag-console"]` covers both.
59
35
  *
60
- * Still declared, never sniffed, and now for a sharper reason than before:
61
- * `bracket-tag-console` and Ruby's `rails-tagged-logging` are the same
62
- * regex with opposite record semantics, so no line can be inspected to
63
- * decide which applies. An unknown name is refused, not skipped.
36
+ * Still declared, never sniffed: `bracket-tag-console` and Ruby's
37
+ * `rails-tagged-logging` are the same regex with opposite record
38
+ * semantics, so no line can decide which applies. Unknown name is refused, not skipped.
64
39
  */
65
40
  readonly logPatterns?: readonly string[];
66
41
  }
@@ -1 +1 @@
1
- {"version":3,"file":"python-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/python-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,sCAAsC,CAAC;AAUpH,MAAM,WAAW,2BAA2B;IAC1C,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;;;;;;;;;;;;OAiBG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,2BAAgC,GAAG,cAAc,CA6DpG"}
1
+ {"version":3,"file":"python-runtime-adapter.d.ts","sourceRoot":"","sources":["../src/python-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAA2C,MAAM,sCAAsC,CAAC;AAUpH,MAAM,WAAW,2BAA2B;IAC1C,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C;AAED,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,2BAAgC,GAAG,cAAc,CAiDpG"}
@@ -1,23 +1,12 @@
1
1
  /**
2
- * The concrete `RuntimeAdapter` for Python — the second language, and the
3
- * first one to test whether the seam `@descryy/runtime-backend-observation`
4
- * defines is genuinely language-neutral or merely V8-shaped.
5
- *
6
- * It was not, in two places, and both were fixed in the seam rather than
7
- * worked around here:
8
- *
9
- * - `LineClassifier` had no notion of a line that ends a block and belongs
10
- * to it, because a V8 header names its exception up front. A CPython
11
- * traceback names it last. `isTrailer` is that concept, added
12
- * language-neutrally.
13
- * - `StackTrace.frames` had no documented order, because with one language
14
- * nothing ever had to convert. Python prints the opposite of V8. The
15
- * contract now states most-recent-first and this adapter reverses.
16
- *
17
- * Both are recorded because they are the general lesson of adding a second
18
- * anything: a seam with one implementation is a seam in name only, and the
19
- * defects it hides are silent ones — a truncated traceback and a
20
- * backwards frame list both look like well-formed output.
2
+ * The concrete RuntimeAdapter for Python — the second language, first to
3
+ * test whether @descryy/runtime-backend-observation's seam is genuinely
4
+ * language-neutral or merely V8-shaped. It wasn't, in two places, fixed in
5
+ * the seam rather than worked around here:
6
+ * - LineClassifier had no `isTrailer` concept (a V8 header names its
7
+ * exception up front; CPython names it last) — added language-neutrally.
8
+ * - StackTrace.frames had no documented order (Python prints the opposite
9
+ * of V8) — contract now states most-recent-first, this adapter reverses.
21
10
  */
22
11
  import { createLogCollector } from "@descryy/runtime-backend-observation";
23
12
  import { createPythonLineClassifier } from "./python-frame-parsing.js";
@@ -34,37 +23,25 @@ export function createPythonRuntimeAdapter(options = {}) {
34
23
  }
35
24
  const patternNames = options.logPatterns ?? (options.server === undefined ? null : [options.server]);
36
25
  const registry = patternNames === null ? null : pythonLogPatternRegistry();
37
- // Names are checked HERE, not inside `createBackendCollectors`. An unknown
38
- // pattern name has to fail where the caller declared it: a misspelling that
39
- // only threw once a process was already spawned, or -- worse -- that
40
- // resolved to no envelope at all, would be silence exactly where this
41
- // option exists to remove it. The envelope itself is still built per
42
- // collector below, because it is stateful.
26
+ // Checked HERE, not inside createBackendCollectors: an unknown pattern name
27
+ // must fail where the caller declared it, not silently once a process is spawned.
43
28
  if (registry !== null && patternNames !== null)
44
29
  registry.envelope(patternNames);
45
30
  function createBackendCollectors(sources) {
46
31
  return sources.map((source) => createLogCollector({
47
32
  source,
48
- // One classifier per collector, never shared: it carries per-stream
49
- // state, and two processes' output must not prime each other.
33
+ // One classifier per collector: carries per-stream state, must not be shared.
50
34
  classifier: createPythonLineClassifier(),
51
35
  stackTraceParser,
52
- // One envelope per collector, never shared: it holds the current
53
- // record's fields, and two processes' output must not attribute
54
- // each other's. For uvicorn its whole contribution is `startsRecord`
55
- // — nothing is stripped from a Python traceback (RT-106); for the
56
- // console patterns it also strips the tag run.
36
+ // One envelope per collector: holds current record's fields, must not be shared.
57
37
  ...(registry === null || patternNames === null ? {} : { envelope: registry.envelope(patternNames) }),
58
38
  ...(options.service !== undefined ? { service: options.service } : {}),
59
39
  }));
60
40
  }
61
41
  /**
62
- * CPython has no `--import`. It runs `sitecustomize.py` from anywhere on
63
- * `PYTHONPATH` at startup, which is the earliest application-independent
64
- * hook available, so the preload directory goes on `PYTHONPATH` --
65
- * *prepended*, never replacing: a target whose own `PYTHONPATH` matters
66
- * would otherwise stop finding its own modules, which is breaking the
67
- * application in order to watch it.
42
+ * CPython has no `--import`; it runs `sitecustomize.py` from anywhere on
43
+ * PYTHONPATH at startup, the earliest hook available. Prepended, never
44
+ * replacing — a target's own PYTHONPATH must still resolve its own modules.
68
45
  */
69
46
  function outboundHttpLaunch() {
70
47
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"python-runtime-adapter.js","sourceRoot":"","sources":["../src/python-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,kCAAkC,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AA+CtF,MAAM,UAAU,0BAA0B,CAAC,UAAuC,EAAE;IAClF,MAAM,sBAAsB,GAAG,kCAAkC,EAAE,CAAC;IACpE,MAAM,gBAAgB,GAAG,4BAA4B,CAAC,sBAAsB,CAAC,CAAC;IAE9E,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,+GAA+G;YAC7G,gGAAgG,CACnG,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACrG,MAAM,QAAQ,GAAG,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB,EAAE,CAAC;IAC3E,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IACrE,sEAAsE;IACtE,qEAAqE;IACrE,2CAA2C;IAC3C,IAAI,QAAQ,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI;QAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEhF,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,oEAAoE;YACpE,8DAA8D;YAC9D,UAAU,EAAE,0BAA0B,EAAE;YACxC,gBAAgB;YAChB,iEAAiE;YACjE,gEAAgE;YAChE,qEAAqE;YACrE,kEAAkE;YAClE,+CAA+C;YAC/C,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACpG,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CACH,CAAC;IACJ,CAAC;IAED;;;;;;;OAOG;IACH,SAAS,kBAAkB;QACzB,OAAO;YACL,GAAG,EAAE,EAAE,UAAU,EAAE,gCAAgC,EAAE;YACrD,iBAAiB,EAAE,CAAC,YAAY,CAAC;SAClC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,QAAQ;QAClB,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;QACvB,kBAAkB;KACnB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"python-runtime-adapter.js","sourceRoot":"","sources":["../src/python-runtime-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,sCAAsC,CAAC;AAE1E,OAAO,EAAE,0BAA0B,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,kCAAkC,EAAE,MAAM,sCAAsC,CAAC;AAC1F,OAAO,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,gCAAgC,EAAE,MAAM,oCAAoC,CAAC;AAiCtF,MAAM,UAAU,0BAA0B,CAAC,UAAuC,EAAE;IAClF,MAAM,sBAAsB,GAAG,kCAAkC,EAAE,CAAC;IACpE,MAAM,gBAAgB,GAAG,4BAA4B,CAAC,sBAAsB,CAAC,CAAC;IAE9E,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,KAAK,SAAS,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,+GAA+G;YAC7G,gGAAgG,CACnG,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACrG,MAAM,QAAQ,GAAG,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB,EAAE,CAAC;IAC3E,4EAA4E;IAC5E,kFAAkF;IAClF,IAAI,QAAQ,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI;QAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAEhF,SAAS,uBAAuB,CAAC,OAAuC;QACtE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAC5B,kBAAkB,CAAC;YACjB,MAAM;YACN,8EAA8E;YAC9E,UAAU,EAAE,0BAA0B,EAAE;YACxC,gBAAgB;YAChB,iFAAiF;YACjF,GAAG,CAAC,QAAQ,KAAK,IAAI,IAAI,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACpG,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvE,CAAC,CACH,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,SAAS,kBAAkB;QACzB,OAAO;YACL,GAAG,EAAE,EAAE,UAAU,EAAE,gCAAgC,EAAE;YACrD,iBAAiB,EAAE,CAAC,YAAY,CAAC;SAClC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,QAAQ;QAClB,gBAAgB;QAChB,sBAAsB;QACtB,uBAAuB;QACvB,kBAAkB;KACnB,CAAC;AACJ,CAAC"}
@@ -1,32 +1,19 @@
1
1
  /**
2
- * Python `SourceLocationResolver`.
2
+ * Python SourceLocationResolver.
3
3
  *
4
- * **Every location is `self-contained`, and that is a positive claim rather
5
- * than a fallback.** CPython captures the path and line of the `.py` file it
6
- * is actually executing; there is no compilation step, no separate map, and
7
- * therefore nothing that can drift or go missing. The frame IS the answer.
8
- * Contrast the Node resolver, which must decide between four values because
9
- * a source map is a second file that can be absent, stale, or wrong.
4
+ * Every location is `self-contained` (a positive claim, not a fallback):
5
+ * CPython captures the path/line of the .py file it's actually executing,
6
+ * no compile step or map to drift. Contrast the Node resolver, which must
7
+ * choose between four values because a source map can be absent/stale/wrong.
10
8
  *
11
- * This is the first resolver in the codebase for which the reliability
12
- * decision is genuinely trivial, and the RT-003 enum already anticipates it:
13
- * `self-contained` means "nothing was lost", not "we didn't look".
9
+ * Deliberately does not check the file exists on disk or still matches what
10
+ * ran — `self-contained` is a statement about mechanism, not the working
11
+ * tree, and a stat call per frame buys only latency. Pseudo-files
12
+ * (`<string>`, `<stdin>`, `<frozen importlib._bootstrap>`) pass through verbatim.
14
13
  *
15
- * **What this deliberately does not do:** check that the file exists on
16
- * disk, or that its current contents still match what ran. Neither would
17
- * change the reliability value — `self-contained` is a statement about the
18
- * *mechanism*, not about whether the working tree has since been edited —
19
- * and a stat call per frame would buy nothing but latency. CPython's
20
- * pseudo-files (`<string>`, `<stdin>`, `<frozen importlib._bootstrap>`) are
21
- * passed through verbatim for the same reason: they are the honest location,
22
- * and rewriting them to null would discard real information.
23
- *
24
- * `column` is always null. CPython does not print columns; 3.11+ prints a
25
- * caret line under the failing expression, from which a column *could* be
26
- * inferred by counting characters. That inference is not made here — it
27
- * would be a derived guess presented in a field callers read as captured
28
- * fact, and the caret line is preserved verbatim in `StackFrame.raw`
29
- * regardless.
14
+ * `column` is always null — CPython doesn't print one. 3.11+'s caret line
15
+ * could be used to infer a column by counting characters, but that would be
16
+ * a derived guess in a field callers read as fact; the caret line stays verbatim in `StackFrame.raw`.
30
17
  */
31
18
  import type { SourceLocationResolver } from "@descryy/runtime-backend-observation";
32
19
  export declare function createPythonSourceLocationResolver(): SourceLocationResolver;
@@ -1 +1 @@
1
- {"version":3,"file":"python-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/python-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAErG,wBAAgB,kCAAkC,IAAI,sBAAsB,CAiB3E"}
1
+ {"version":3,"file":"python-source-location-resolver.d.ts","sourceRoot":"","sources":["../src/python-source-location-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAoB,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAErG,wBAAgB,kCAAkC,IAAI,sBAAsB,CAc3E"}