codetac 0.1.0 → 0.2.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.
- package/package.json +10 -5
- package/readme.md +81 -14
- package/src/ai.mjs +4 -2
- package/src/boundaries.mjs +12 -9
- package/src/browser/bar.js +5 -3
- package/src/cli.mjs +251 -33
- package/src/detect-python.mjs +286 -0
- package/src/detect.mjs +21 -6
- package/src/diagnose.mjs +35 -5
- package/src/digest.mjs +18 -4
- package/src/page.mjs +19 -7
- package/src/panel.mjs +56 -14
- package/src/python/codetac_py/__init__.py +88 -0
- package/src/python/codetac_py/boundaries.py +435 -0
- package/src/python/codetac_py/capture.py +351 -0
- package/src/python/codetac_py/context.py +50 -0
- package/src/python/codetac_py/detail.py +333 -0
- package/src/python/codetac_py/files.py +135 -0
- package/src/python/codetac_py/frameworks.py +72 -0
- package/src/python/codetac_py/hooks.py +72 -0
- package/src/python/codetac_py/jinja_map.py +132 -0
- package/src/python/codetac_py/network.py +704 -0
- package/src/python/codetac_py/page.py +513 -0
- package/src/python/codetac_py/project.py +60 -0
- package/src/python/codetac_py/redact.py +104 -0
- package/src/python/codetac_py/servers.py +514 -0
- package/src/python/codetac_py/sitecustomize.py +62 -0
- package/src/python/codetac_py/writer.py +237 -0
- package/src/python/probe.py +99 -0
- package/src/recording.mjs +22 -4
- package/src/runtime.mjs +13 -1
- package/src/sentences.mjs +4 -0
- package/src/store.mjs +61 -9
package/src/panel.mjs
CHANGED
|
@@ -32,6 +32,8 @@ function source(run, file, line, endLine) {
|
|
|
32
32
|
// Never hidden files (.env and similar), and only files with recorded functions.
|
|
33
33
|
if (within.split(sep).some(part => part.startsWith('.') && part !== '.next')) return null;
|
|
34
34
|
if (!store.recordedFile(run, file) && !view.browserFile(run, real)) return null;
|
|
35
|
+
// A step without a line (opaque) has no code to show.
|
|
36
|
+
if (!Number.isFinite(line) || line < 1) return null;
|
|
35
37
|
const lines = readFileSync(real, 'utf8').split('\n');
|
|
36
38
|
const start = Math.max(1, line);
|
|
37
39
|
const end = Math.min(lines.length, endLine && endLine >= start ? Math.min(endLine, start + 400) : start + 40);
|
|
@@ -202,7 +204,7 @@ const page = `<!doctype html>
|
|
|
202
204
|
<head>
|
|
203
205
|
<meta charset="utf-8">
|
|
204
206
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
205
|
-
<title>CodeTAC —
|
|
207
|
+
<title>CodeTAC — dossiers</title>
|
|
206
208
|
<style>
|
|
207
209
|
:root { --bg:#f7f7f5; --panel:#fff; --text:#1d1d1b; --muted:#6b6b66; --line:#e4e3de; --accent:#2f5bd3;
|
|
208
210
|
--db:#0f7b5f; --http:#6a4bc4; --ia:#b4531f; --mail:#1f73b4; --pay:#9b2c86; --file:#7a6a12; --auth:#3d6b2f; --error:#c0392b; --code:#f1f0ec;
|
|
@@ -390,6 +392,10 @@ function relative(root, file) { return root && file && file.startsWith(root + '/
|
|
|
390
392
|
// functions with steps inside open with the arrow.
|
|
391
393
|
// Rows keep what the step box needs (code, values, questions) by id.
|
|
392
394
|
const stepData = new Map();
|
|
395
|
+
// Work done after the response was sent (FastAPI BackgroundTasks, teardown).
|
|
396
|
+
function afterTag(n) {
|
|
397
|
+
return n.afterResponse ? ' <span class="tag" title="Correu depois de a resposta ter sido enviada ao browser (por exemplo, tarefas em segundo plano).">depois da resposta</span>' : '';
|
|
398
|
+
}
|
|
393
399
|
function editorLink(file, line) {
|
|
394
400
|
if (!aiConfig || !aiConfig.editor || !file || file[0] !== '/') return '';
|
|
395
401
|
return '<a class="mini" href="' + esc(aiConfig.editor + '://file' + encodeURI(file) + ':' + (line || 1) + ':1') + '" title="Abrir no editor na linha ' + esc(line) + '">abrir no editor</a>';
|
|
@@ -420,19 +426,21 @@ function rowHtml(n, c, depth, open) {
|
|
|
420
426
|
if (n.type === 'group') {
|
|
421
427
|
row = '<div class="step grp' + (n.errors ? ' warn' : '') + '" ' + pad + '>' + caret + '<span class="body"><span class="tag k-grupo">' + n.count + ' passos</span><span class="sent">' + esc(n.sentence) + '</span></span><span class="time">' + ms(n.durationMs) + '</span></div>';
|
|
422
428
|
} else if (n.type === 'function') {
|
|
423
|
-
|
|
429
|
+
// Without a line (a template whose lines could not be mapped): the file only.
|
|
430
|
+
const where = n.file ? relative(c.root, n.file) + (n.line != null ? ':' + n.line : ' (sem linhas)') : '';
|
|
424
431
|
// A function with nothing inside is named in its parent's sentence ("chama …"):
|
|
425
432
|
// the grouped view leaves it out; "Ver tudo" shows it in place.
|
|
426
433
|
const internal = depth > 0 && !inner.length && !n.error && !n.detail;
|
|
427
434
|
const on = asked(c, n);
|
|
428
435
|
row = '<div class="step fn' + (n.error ? ' error' : '') + (internal ? ' internal' : '') + '" ' + pad + ' data-step="' + esc(n.id) + '">' + caret +
|
|
429
436
|
'<span class="body"><span class="sent" data-node="' + esc(n.id) + '" data-facts="' + esc(n.purpose.text) + '">' + esc(n.purpose.text) + '</span>' +
|
|
430
|
-
'<span class="where" title="' + esc(where) + '">' + esc(n.function) + ' · ' + esc(where.split('/').pop()) + '</span>' + (n.error ? ' <span class="tag" style="color:var(--error)">erro</span>' : '') +
|
|
431
|
-
|
|
432
|
-
|
|
437
|
+
'<span class="where" title="' + esc(where) + '">' + esc(n.function) + (where ? ' · ' + esc(where.split('/').pop()) : '') + '</span>' + (n.error ? ' <span class="tag" style="color:var(--error)">erro</span>' : '') +
|
|
438
|
+
(n.opaque ? ' <span class="tag" title="Esta parte corre, mas o CodeTAC não consegue ver o que faz por dentro.">opaco</span>' : '') + afterTag(n) +
|
|
439
|
+
(n.opaque ? '' : '<span class="acts"><button class="mini' + (on ? ' on' : '') + '" data-detalhe="' + esc(n.id) + '" title="' + (on ? 'Detalhe pedido: a próxima execução grava os valores. Carregue para desligar.' : 'Gravar os valores de entrada e saída e as linhas executadas desta função, a partir da próxima ação') + '">' + (on ? 'detalhe ligado' : 'pedir detalhe') + '</button>' +
|
|
440
|
+
editorLink(n.file, n.line) + '</span>') + '</span><span class="time">' + ms(n.durationMs) + '</span></div>' + (n.detail ? valuesHtml(n, valuesPad) : '');
|
|
433
441
|
} else {
|
|
434
442
|
const r = n.result || {};
|
|
435
|
-
row = '<div class="step boundary' + (n.error || r.error ? ' error' : '') + '" ' + pad + ' data-step="' + esc(n.id) + '">' + caret + '<span class="body"><span class="tag k-' + esc(n.kind) + '">' + esc(kindLabel[n.kind] || n.kind) + '</span>' +
|
|
443
|
+
row = '<div class="step boundary' + (n.error || r.error ? ' error' : '') + '" ' + pad + ' data-step="' + esc(n.id) + '">' + caret + '<span class="body"><span class="tag k-' + esc(n.kind) + '">' + esc(kindLabel[n.kind] || n.kind) + '</span>' + afterTag(n) +
|
|
436
444
|
'<span class="sent" data-node="' + esc(n.id) + '" data-facts="' + esc(n.purpose.text) + '">' + esc(n.purpose.text) + '</span>' +
|
|
437
445
|
(n.sql ? '<span class="detail sql">' + esc(n.sql) + '</span>' : '') +
|
|
438
446
|
(r.promptExcerpt || n.promptExcerpt ? '<span class="detail">pedido: «' + esc(r.promptExcerpt || n.promptExcerpt) + '»</span>' : '') +
|
|
@@ -491,6 +499,7 @@ const REASONS = {
|
|
|
491
499
|
'generator-skipped': 'generators: correm, mas não aparecem na sequência',
|
|
492
500
|
'parameter-redeclaration-skipped': 'funções que redeclaram um parâmetro: correm, mas não aparecem na sequência',
|
|
493
501
|
'direct-eval-skipped': 'funções com eval direto: correm, mas não aparecem na sequência',
|
|
502
|
+
'template-lines-unmapped': 'templates cujas linhas não foi possível ligar ao ficheiro: aparecem como um só passo, sem linhas',
|
|
494
503
|
};
|
|
495
504
|
function minimalHtml(d) {
|
|
496
505
|
if (d.level !== 'minimo') return '';
|
|
@@ -595,6 +604,7 @@ async function loadAction(id) {
|
|
|
595
604
|
let html = '<h2>' + esc(d.label) + '</h2><p class="summary" data-node="action" data-facts="' + esc(d.digest.summary) + '">' + esc(d.digest.summary) + '</p><p class="note">' + time(d.startedAt) + (d.durationMs ? ' · ação de ' + ms(d.durationMs) : '') +
|
|
596
605
|
' · no browser: elemento, componente, handler e pedidos (sem funções uma a uma) · no servidor: nível ' + esc(d.level) + '</p>';
|
|
597
606
|
html += minimalHtml(d) + '<p id="ai"></p>' + TOOLBAR + detailStateHtml(run);
|
|
607
|
+
const explained = new Set(); // origin notes already explained in full in this action
|
|
598
608
|
for (const item of d.digest.timeline) {
|
|
599
609
|
if (item.type === 'dev-group') {
|
|
600
610
|
html += '<div class="node closed"><div class="step grp"><button class="caret">▸</button><span class="body"><span class="tag k-grupo">' + item.items.length + '</span><span class="sent">' + esc(item.sentence) + '</span></span><span class="time"></span></div>' +
|
|
@@ -623,7 +633,9 @@ async function loadAction(id) {
|
|
|
623
633
|
esc(b.method) + ' ' + esc(b.path) + (b.queryKeys && b.queryKeys.length ? '?' + b.queryKeys.map(k => esc(k) + '=…').join('&') : '') + '</span> ' +
|
|
624
634
|
'<span class="where">' + (b.status != null ? 'estado ' + b.status : b.error ? 'falhou' : 'sem resposta') + ' · ' + ms(b.durationMs) + '</span>' +
|
|
625
635
|
(chainHtml || via ? '<div class="meta">chamado por: ' + (chainHtml || '<span class="where">(sem posição resolvida no projeto)</span>') + via + '</div>' : '') + '</div>';
|
|
626
|
-
|
|
636
|
+
const note = originNote(item, d.origin, explained.has(item.probable ? 'provavel' : 'sem'));
|
|
637
|
+
if (note) explained.add(item.probable ? 'provavel' : 'sem');
|
|
638
|
+
html += note + serverHtml(item.server, b.sameOrigin, b);
|
|
627
639
|
} else if (item.type === 'navigation') {
|
|
628
640
|
const text = item.kind === 'saída da página' ? 'saiu de ' + esc(item.path) + ' (o browser carrega um documento novo)' : esc(item.kind) + ' → ' + esc(item.path);
|
|
629
641
|
html += '<div class="step plain"><span><span class="tag k-browser">navegação</span><span class="name">' + text + '</span></span><span class="time"></span></div>';
|
|
@@ -659,14 +671,38 @@ async function loadAction(id) {
|
|
|
659
671
|
actionTimer = setTimeout(() => loadAction(id), 1500);
|
|
660
672
|
} else startPurposes('actions', id);
|
|
661
673
|
}
|
|
674
|
+
// DP3: a request of the page to another origin of the same machine (the API
|
|
675
|
+
// on another port, without a proxy), in plain language.
|
|
676
|
+
// The full explanation once per action; a short line for the next requests.
|
|
677
|
+
function originNote(item, pageOrigin, brief) {
|
|
678
|
+
const b = item.browser;
|
|
679
|
+
if (b.sameOrigin || !b.host) return '';
|
|
680
|
+
let page = '';
|
|
681
|
+
try { page = new URL(pageOrigin).hostname; } catch {}
|
|
682
|
+
const name = b.host.replace(/:\\d+$/, '').replace(/^\\[|\\]$/g, '');
|
|
683
|
+
const local = ['localhost', '127.0.0.1', '::1', page].includes(name);
|
|
684
|
+
const proxy = ' Para uma ligação exata, use o proxy do servidor de desenvolvimento (no Vite, <code>server.proxy</code>).';
|
|
685
|
+
if (item.probable && brief) return '<p class="note probable"><b>Ligação provável</b> (como acima).</p>';
|
|
686
|
+
if (!item.probable && local && brief) return '<p class="note probable"><b>Sem ligação ao servidor</b> (como acima).</p>';
|
|
687
|
+
if (item.probable) {
|
|
688
|
+
return '<p class="note probable"><b>Ligação provável.</b> O pedido foi para outra origem (' + esc(b.host) + '), onde o CodeTAC não o pode marcar sem mudar a app: ' +
|
|
689
|
+
'foi ligado ao pedido que esse servidor recebeu desta página com o mesmo método e caminho, à mesma hora (pedidos iguais em simultâneo podem trocar-se).' + proxy + '</p>';
|
|
690
|
+
}
|
|
691
|
+
if (!local) return '';
|
|
692
|
+
return '<p class="note probable"><b>Sem ligação ao servidor.</b> O pedido foi para outra origem (' + esc(b.host) + ') e o CodeTAC não encontrou o pedido nesse servidor: ' +
|
|
693
|
+
'pode não estar a ser observado (arranque-o também com o CodeTAC).' + proxy + '</p>';
|
|
694
|
+
}
|
|
695
|
+
|
|
662
696
|
function serverHtml(list, sameOrigin, browser) {
|
|
663
697
|
if (!list || !list.length) {
|
|
664
698
|
return sameOrigin ? '<div class="server-part"><p class="note" style="margin:4px 0">O servidor não registou este pedido (servido sem captura, por exemplo por um ficheiro estático ou por outro processo).</p></div>' : '';
|
|
665
699
|
}
|
|
666
700
|
const same = d => browser && list.length === 1 && browser.method === d.request.method && browser.path === d.request.path && browser.status === d.request.status;
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
701
|
+
// A part with nothing inside (a CORS preflight, the hop of a proxy) takes one line.
|
|
702
|
+
const empty = '<p class="note" style="margin:4px 0">Nenhuma função do projeto nem fronteira neste pedido.</p>';
|
|
703
|
+
return list.map(d => '<div class="server-part">' + (same(d) ? (d.steps.length ? '' : empty) : '<div class="meta"><span class="tag k-servidor">servidor</span>' + esc(d.request.method) + ' ' + esc(d.request.path) +
|
|
704
|
+
' · estado ' + esc(d.request.status ?? 'em curso') + ' · ' + ms(d.request.durationMs) + (d.steps.length ? '' : ' · sem funções do projeto nem fronteiras') + '</div>') +
|
|
705
|
+
(d.steps.length ? digestHtml(d) : '') + '</div>').join('');
|
|
670
706
|
}
|
|
671
707
|
|
|
672
708
|
detail.addEventListener('click', async event => {
|
|
@@ -732,12 +768,17 @@ async function openStep(id) {
|
|
|
732
768
|
const { node: n, c } = stepData.get(id);
|
|
733
769
|
const box = document.getElementById('code');
|
|
734
770
|
let html = '<section class="box">';
|
|
735
|
-
if (n.type === 'function') {
|
|
771
|
+
if (n.type === 'function' && n.opaque) {
|
|
772
|
+
// Nothing to show inside: no code, no values.
|
|
773
|
+
html += '<h3>' + esc(n.function) + (n.file ? ' — ' + esc(relative(c.root, n.file)) : '') + '</h3>' +
|
|
774
|
+
'<p class="note" style="margin:2px 0 6px">' + esc(n.purpose.text) + '. O tempo e o resultado (erro ou não) são reais; o que se passa lá dentro não é visível.</p>';
|
|
775
|
+
} else if (n.type === 'function') {
|
|
736
776
|
const params = new URLSearchParams({ run: c.run, file: n.file, line: n.line, end: n.endLine ?? '' });
|
|
737
|
-
const result =
|
|
777
|
+
const result = n.line == null ? { error: 'Sem linhas: o CodeTAC não conseguiu ligar este passo às linhas do ficheiro.' }
|
|
778
|
+
: await (await fetch('/api/source?' + params)).json();
|
|
738
779
|
const on = asked(c, n);
|
|
739
780
|
const wholeFile = (detailSpecs[c.run] || { files: [] }).files.includes(n.file);
|
|
740
|
-
html += '<h3>' + esc(n.function) + ' — ' + esc(result.file || relative(c.root, n.file)) + ':' + n.line + ' ' + editorLink(n.file, n.line) + '</h3>' +
|
|
781
|
+
html += '<h3>' + esc(n.function) + ' — ' + esc(result.file || relative(c.root, n.file)) + (n.line != null ? ':' + n.line : '') + ' ' + editorLink(n.file, n.line ?? 1) + '</h3>' +
|
|
741
782
|
'<p class="note" style="margin:2px 0 6px">' + esc(n.purpose.text) + '</p>' +
|
|
742
783
|
'<div class="toolbar"><button data-detalhe="' + esc(n.id) + '">' + (on && !wholeFile ? 'Desligar o detalhe desta função' : 'Pedir detalhe desta função') + '</button>' +
|
|
743
784
|
'<button data-ficheiro="' + esc(n.file) + '" data-run="' + esc(c.run) + '">' + (wholeFile ? 'Desligar o detalhe do ficheiro' : 'Pedir detalhe de todo o ficheiro') + '</button></div>';
|
|
@@ -771,7 +812,8 @@ async function askQuestion(id) {
|
|
|
771
812
|
const a = await response.json();
|
|
772
813
|
if (a.error) { out.innerHTML = '<p class="note">' + esc(a.error) + '</p>'; return; }
|
|
773
814
|
if (!a.available) { out.innerHTML = '<p class="note">' + esc(a.text) + '</p>'; return; }
|
|
774
|
-
const source = a.model ? 'Resposta de ' + a.model + (a.local ? ' (modelo local)' : '') + (a.valuesSent ? ', com os valores gravados.'
|
|
815
|
+
const source = a.model ? 'Resposta de ' + a.model + (a.local ? ' (modelo local)' : '') + (a.valuesSent ? ', com os valores gravados.'
|
|
816
|
+
: a.valuesWithheld ? ', sem os valores gravados: não são enviados a modelos fora desta máquina (só as linhas executadas e o código redigido).' : '.') : '';
|
|
775
817
|
out.innerHTML = '<div class="answer' + (a.known ? '' : ' unknown') + (a.rejected ? ' rejected' : '') + '">' +
|
|
776
818
|
(a.rejected ? '<p class="note" style="color:var(--error);margin:0 0 4px">Resposta não verificada (' + esc(a.rejected) + '). Confirme no código.</p>' : '') +
|
|
777
819
|
(!a.known ? '<p class="note" style="margin:0 0 4px">Os factos gravados não chegam para responder com certeza:</p>' : '') +
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""CodeTAC capture for Python apps (FastAPI, Flask).
|
|
2
|
+
|
|
3
|
+
Loaded by sitecustomize.py only when CODETAC_RUN is set. It writes the same
|
|
4
|
+
JSONL events as the Node runtime (src/runtime.mjs), so the store, the panel
|
|
5
|
+
and the bar need no Python branches.
|
|
6
|
+
|
|
7
|
+
This file, writer.py and redact.py must stay importable on old Pythons
|
|
8
|
+
(3.8+): below the minimum the capture falls back to the minimal mode instead
|
|
9
|
+
of breaking the app. capture.py needs 3.12 (sys.monitoring).
|
|
10
|
+
"""
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
MINIMUM = (3, 12)
|
|
16
|
+
|
|
17
|
+
level = None
|
|
18
|
+
root = None
|
|
19
|
+
reason = None
|
|
20
|
+
writer = None
|
|
21
|
+
monitor = None # capture.Capture, when the project's functions are followed
|
|
22
|
+
# 'supervisor' in a process that only watches files or manages workers (the
|
|
23
|
+
# reloader of werkzeug, uvicorn --reload, fastapi dev): it serves nothing.
|
|
24
|
+
role = 'app'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def start():
|
|
28
|
+
global level, reason, writer, monitor, root
|
|
29
|
+
run = os.environ.get('CODETAC_RUN', '')
|
|
30
|
+
if not re.match(r'^[a-zA-Z0-9_-]{1,80}$', run):
|
|
31
|
+
sys.stderr.write('codeTAC: CODETAC_RUN: use apenas letras, números, _ ou - (máximo 80). Captura desligada.\n')
|
|
32
|
+
return
|
|
33
|
+
from .writer import Writer, data_directory, install_signals
|
|
34
|
+
root = os.path.realpath(os.environ.get('CODETAC_ROOT') or os.getcwd())
|
|
35
|
+
writer = Writer(os.path.join(data_directory(), run))
|
|
36
|
+
|
|
37
|
+
if os.environ.get('CODETAC_LEVEL') == 'minimo':
|
|
38
|
+
level, reason = 'minimo', os.environ.get('CODETAC_MINIMO_MOTIVO') or 'pedido no arranque'
|
|
39
|
+
elif sys.version_info < MINIMUM:
|
|
40
|
+
level = 'minimo'
|
|
41
|
+
reason = 'o Python %d.%d não permite seguir as funções (é preciso o %d.%d ou mais recente)' % (
|
|
42
|
+
sys.version_info[:2] + MINIMUM)
|
|
43
|
+
else:
|
|
44
|
+
from .capture import Capture, NoToolId
|
|
45
|
+
try:
|
|
46
|
+
monitor = Capture(writer, root)
|
|
47
|
+
level, reason = 'normal', None
|
|
48
|
+
except NoToolId as error:
|
|
49
|
+
level, reason = 'minimo', str(error)
|
|
50
|
+
|
|
51
|
+
event = {'type': 'capture-start', 'root': root, 'python': '%d.%d.%d' % sys.version_info[:3], 'level': level}
|
|
52
|
+
if reason:
|
|
53
|
+
event['reason'] = reason
|
|
54
|
+
writer.start(event)
|
|
55
|
+
install_signals(writer.flush)
|
|
56
|
+
if monitor is not None:
|
|
57
|
+
monitor.start()
|
|
58
|
+
from .servers import install_hooks
|
|
59
|
+
install_hooks()
|
|
60
|
+
from .boundaries import install as install_boundaries
|
|
61
|
+
install_boundaries(root, data_directory())
|
|
62
|
+
from .frameworks import install as install_frameworks
|
|
63
|
+
install_frameworks()
|
|
64
|
+
if hasattr(os, 'register_at_fork'):
|
|
65
|
+
os.register_at_fork(after_in_child=_after_fork)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def mark_supervisor(server):
|
|
69
|
+
"""This process supervises the one that serves: it stops following functions."""
|
|
70
|
+
global role
|
|
71
|
+
if role == 'supervisor':
|
|
72
|
+
return
|
|
73
|
+
role = 'supervisor'
|
|
74
|
+
if monitor is not None:
|
|
75
|
+
monitor.pause()
|
|
76
|
+
# Only when the process already left a file (it imported the app, as the
|
|
77
|
+
# reloader of flask run --debug does): it says why nothing else follows.
|
|
78
|
+
if writer.active:
|
|
79
|
+
writer.emit({'type': 'process', 'role': 'supervisor', 'server': server})
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _after_fork():
|
|
83
|
+
"""A worker forked by a supervisor (gunicorn) serves: it follows functions again."""
|
|
84
|
+
global role
|
|
85
|
+
if role == 'supervisor':
|
|
86
|
+
role = 'app'
|
|
87
|
+
if monitor is not None:
|
|
88
|
+
monitor.resume()
|