taskcheck 1.6.2__tar.gz → 1.7.0__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.
- {taskcheck-1.6.2/taskcheck.egg-info → taskcheck-1.7.0}/PKG-INFO +6 -1
- {taskcheck-1.6.2 → taskcheck-1.7.0}/README.md +5 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/pyproject.toml +1 -1
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/__main__.py +2 -2
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/dry_run.py +16 -1
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/install.py +2 -2
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/parallel.py +1 -7
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/report.py +50 -56
- {taskcheck-1.6.2 → taskcheck-1.7.0/taskcheck.egg-info}/PKG-INFO +6 -1
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck.egg-info/SOURCES.txt +2 -0
- taskcheck-1.7.0/tests/test_dry_run.py +21 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_report.py +2 -3
- taskcheck-1.7.0/tests/test_report_columns.py +29 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/LICENSE +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/setup.cfg +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/agent_blocks.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/common.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/google_calendar.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/ical.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/theme.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/timeline.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck/undo.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck.egg-info/dependency_links.txt +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck.egg-info/entry_points.txt +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck.egg-info/requires.txt +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/taskcheck.egg-info/top_level.txt +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_agent_blocks.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_agent_scheduler_integration.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_common.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_google_calendar.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_ical.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_install.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_main.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_parallel.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_timeline.py +0 -0
- {taskcheck-1.6.2 → taskcheck-1.7.0}/tests/test_undo.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -114,6 +114,11 @@ Create a preview with `taskcheck --schedule --dry-run`. It does not update Taskw
|
|
|
114
114
|
saves the generated schedule to the Taskwarrior data directory. Reuse it with `taskcheck --report
|
|
115
115
|
today --dry-run` or `taskcheck --timeline 1w --dry-run`.
|
|
116
116
|
|
|
117
|
+
Report columns use the labels from `uda.<attribute>.label` in the active Taskwarrior configuration.
|
|
118
|
+
Reports show a robot icon beside the task ID for tasks tagged `agent`.
|
|
119
|
+
Dry-run schedules retain custom task attributes, including `agent_weight`.
|
|
120
|
+
When you load an older preview, Taskcheck fills missing task attributes from Taskwarrior and keeps the saved schedule.
|
|
121
|
+
|
|
117
122
|
Timeline rows follow dotted project levels such as `work.client`. Without `--zoom`, the most
|
|
118
123
|
detailed view that fits the terminal width is selected: day, then week, then month. The track uses
|
|
119
124
|
`▶` for a start, `━` for work, `◀` for an end, and `◆` for a deadline. Thin, heavy, and double rules
|
|
@@ -99,6 +99,11 @@ Create a preview with `taskcheck --schedule --dry-run`. It does not update Taskw
|
|
|
99
99
|
saves the generated schedule to the Taskwarrior data directory. Reuse it with `taskcheck --report
|
|
100
100
|
today --dry-run` or `taskcheck --timeline 1w --dry-run`.
|
|
101
101
|
|
|
102
|
+
Report columns use the labels from `uda.<attribute>.label` in the active Taskwarrior configuration.
|
|
103
|
+
Reports show a robot icon beside the task ID for tasks tagged `agent`.
|
|
104
|
+
Dry-run schedules retain custom task attributes, including `agent_weight`.
|
|
105
|
+
When you load an older preview, Taskcheck fills missing task attributes from Taskwarrior and keeps the saved schedule.
|
|
106
|
+
|
|
102
107
|
Timeline rows follow dotted project levels such as `work.client`. Without `--zoom`, the most
|
|
103
108
|
detailed view that fits the terminal width is selected: day, then week, then month. The track uses
|
|
104
109
|
`▶` for a start, `━` for work, `◀` for an end, and `◆` for a deadline. Thin, heavy, and double rules
|
|
@@ -102,8 +102,8 @@ def load_config():
|
|
|
102
102
|
},
|
|
103
103
|
"report": {
|
|
104
104
|
"include_unplanned": true,
|
|
105
|
-
"
|
|
106
|
-
"
|
|
105
|
+
"attributes": ["id", "project", "description", "scheduling_hours"],
|
|
106
|
+
"attributes_unplanned": ["id", "project", "description"],
|
|
107
107
|
"emoji_keywords": {},
|
|
108
108
|
...
|
|
109
109
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import subprocess
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
4
5
|
from taskcheck.undo import get_task_data_dir
|
|
6
|
+
from taskcheck.common import get_task_env
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def get_dry_run_path(taskrc=None):
|
|
@@ -19,4 +21,17 @@ def load_dry_run_results(taskrc=None):
|
|
|
19
21
|
path = get_dry_run_path(taskrc)
|
|
20
22
|
if not path.exists():
|
|
21
23
|
return None
|
|
22
|
-
|
|
24
|
+
results = json.loads(path.read_text())
|
|
25
|
+
if not results:
|
|
26
|
+
return results
|
|
27
|
+
response = subprocess.run(
|
|
28
|
+
["task", "export"], capture_output=True, text=True,
|
|
29
|
+
env=get_task_env(taskrc),
|
|
30
|
+
)
|
|
31
|
+
current_tasks = {
|
|
32
|
+
task["uuid"]: task for task in json.loads(response.stdout)
|
|
33
|
+
}
|
|
34
|
+
return [
|
|
35
|
+
{**current_tasks.get(task.get("uuid"), {}), **task}
|
|
36
|
+
for task in results
|
|
37
|
+
]
|
|
@@ -20,8 +20,8 @@ n_undos = 1
|
|
|
20
20
|
|
|
21
21
|
[report]
|
|
22
22
|
include_unplanned = true
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
attributes = ["id", "project", "description", "scheduling_hours"]
|
|
24
|
+
attributes_unplanned = ["id", "project", "description"]
|
|
25
25
|
# unplanned_max_due = "30d"
|
|
26
26
|
emoji_keywords = {}
|
|
27
27
|
|
|
@@ -293,13 +293,7 @@ def check_tasks_parallel(
|
|
|
293
293
|
scheduling_note += f"{date_str} - {hours_to_pdth(hours)}\n"
|
|
294
294
|
|
|
295
295
|
task_result = {
|
|
296
|
-
|
|
297
|
-
"uuid": task["uuid"],
|
|
298
|
-
"description": task["description"],
|
|
299
|
-
"project": task.get("project", ""),
|
|
300
|
-
"urgency": task.get("urgency", 0),
|
|
301
|
-
"estimated": task["estimated"],
|
|
302
|
-
"due": task.get("due", ""),
|
|
296
|
+
**task,
|
|
303
297
|
"scheduled": start_date,
|
|
304
298
|
"completion_date": end_date,
|
|
305
299
|
"scheduling": scheduling_note.strip(),
|
|
@@ -198,16 +198,10 @@ def get_tasks(config, tasks, year, month, day):
|
|
|
198
198
|
due = due - datetime.today()
|
|
199
199
|
valid_tasks.append(
|
|
200
200
|
{
|
|
201
|
-
|
|
202
|
-
"project": task.get("project", ""),
|
|
203
|
-
"description": task["description"],
|
|
204
|
-
"urgency": task["urgency"],
|
|
201
|
+
**task,
|
|
205
202
|
"scheduling_day": f"{year}-{month}-{day}",
|
|
206
203
|
"scheduling_hours": m.group(1),
|
|
207
|
-
|
|
208
|
-
attr: task.get(attr, "")
|
|
209
|
-
for attr in config.get("additional_attributes", [])
|
|
210
|
-
},
|
|
204
|
+
"tags": task.get("tags", []),
|
|
211
205
|
}
|
|
212
206
|
)
|
|
213
207
|
valid_tasks = sorted(valid_tasks, key=lambda x: x["urgency"], reverse=True)
|
|
@@ -287,7 +281,22 @@ def get_unplanned_tasks(config, tasks, taskrc=None):
|
|
|
287
281
|
|
|
288
282
|
|
|
289
283
|
def generate_report(config, constraint, verbose=False, force_update=False, taskrc=None, scheduling_results=None):
|
|
290
|
-
|
|
284
|
+
from taskcheck.common import get_task_env
|
|
285
|
+
|
|
286
|
+
report_config = dict(config["report"])
|
|
287
|
+
result = subprocess.run(
|
|
288
|
+
["task", "_show"],
|
|
289
|
+
capture_output=True,
|
|
290
|
+
text=True,
|
|
291
|
+
env=get_task_env(taskrc),
|
|
292
|
+
)
|
|
293
|
+
report_config["attribute_labels"] = {
|
|
294
|
+
key[4:-6]: value
|
|
295
|
+
for line in result.stdout.splitlines()
|
|
296
|
+
if "=" in line
|
|
297
|
+
for key, value in [line.split("=", 1)]
|
|
298
|
+
if key.startswith("uda.") and key.endswith(".label") and value
|
|
299
|
+
}
|
|
291
300
|
theme = detect_theme(config=config.get("timeline", {}))
|
|
292
301
|
console = Console()
|
|
293
302
|
|
|
@@ -347,32 +356,38 @@ def display_tasks_table(console, config, tasks, theme="dark"):
|
|
|
347
356
|
|
|
348
357
|
def build_tasks_table(config, tasks, theme="dark"):
|
|
349
358
|
"""Build a Rich table for displaying tasks."""
|
|
359
|
+
return _build_report_table(
|
|
360
|
+
config, tasks, config.get("attributes", ["id", "project", "description", "scheduling_hours"]), theme
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _build_report_table(config, tasks, attributes, theme):
|
|
350
365
|
header_style = "bold cyan" if theme == "dark" else "bold blue"
|
|
351
366
|
table = Table(show_header=True, header_style=header_style)
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
for attr in config.get("additional_attributes", []):
|
|
357
|
-
table.add_column(attr.capitalize(), justify="right")
|
|
358
|
-
|
|
359
|
-
for task in tasks:
|
|
360
|
-
task_id = f"[bold green]#{task['id']}[/bold green]"
|
|
361
|
-
project = task.get("project", "")
|
|
362
|
-
description = Text(task["description"], style="italic")
|
|
363
|
-
hours = f"[yellow]{task['scheduling_hours']}[/yellow]"
|
|
364
|
-
emoji = get_task_emoji(config, task)
|
|
365
|
-
|
|
366
|
-
table.add_row(
|
|
367
|
-
f"{emoji} {task_id}",
|
|
368
|
-
project,
|
|
369
|
-
description,
|
|
370
|
-
hours,
|
|
371
|
-
*[
|
|
372
|
-
tostring(task.get(attr, ""))
|
|
373
|
-
for attr in config.get("additional_attributes", [])
|
|
374
|
-
],
|
|
367
|
+
labels = {"id": "Task", "scheduling_hours": "Time"}
|
|
368
|
+
for attr in attributes:
|
|
369
|
+
label = config.get("attribute_labels", {}).get(
|
|
370
|
+
attr, labels.get(attr, attr.capitalize())
|
|
375
371
|
)
|
|
372
|
+
if attr in ("id", "project"):
|
|
373
|
+
table.add_column(label, style="dim", width=12)
|
|
374
|
+
else:
|
|
375
|
+
table.add_column(label, justify="left" if attr == "description" else "right")
|
|
376
|
+
if not attributes:
|
|
377
|
+
return table
|
|
378
|
+
for task in tasks:
|
|
379
|
+
cells = []
|
|
380
|
+
for attr in attributes:
|
|
381
|
+
if attr == "id":
|
|
382
|
+
icon = " 🤖" if "agent" in task.get("tags", []) else ""
|
|
383
|
+
cells.append(f"{get_task_emoji(config, task)} [bold green]#{task['id']}[/bold green]{icon}")
|
|
384
|
+
elif attr == "description":
|
|
385
|
+
cells.append(Text(task.get(attr, ""), style="italic"))
|
|
386
|
+
elif attr == "scheduling_hours":
|
|
387
|
+
cells.append(f"[yellow]{task.get(attr, '')}[/yellow]")
|
|
388
|
+
else:
|
|
389
|
+
cells.append(tostring(task.get(attr, "")))
|
|
390
|
+
table.add_row(*cells)
|
|
376
391
|
return table
|
|
377
392
|
|
|
378
393
|
|
|
@@ -417,27 +432,6 @@ def display_unplanned_tasks(console, config, tasks, theme="dark"):
|
|
|
417
432
|
|
|
418
433
|
def build_unplanned_tasks_table(config, tasks, theme="dark"):
|
|
419
434
|
"""Build a Rich table for displaying unplanned tasks."""
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
table.add_column("Project", style="dim", width=12)
|
|
424
|
-
table.add_column("Description")
|
|
425
|
-
for attr in config.get("additional_attributes_unplanned", []):
|
|
426
|
-
table.add_column(attr.capitalize(), justify="right")
|
|
427
|
-
|
|
428
|
-
for task in tasks:
|
|
429
|
-
task_id = f"[bold green]#{task['id']}[/bold green]"
|
|
430
|
-
project = task.get("project", "")
|
|
431
|
-
description = Text(task["description"], style="italic")
|
|
432
|
-
emoji = get_task_emoji(config, task)
|
|
433
|
-
|
|
434
|
-
table.add_row(
|
|
435
|
-
f"{emoji} {task_id}",
|
|
436
|
-
project,
|
|
437
|
-
description,
|
|
438
|
-
*[
|
|
439
|
-
tostring(task.get(attr, ""))
|
|
440
|
-
for attr in config.get("additional_attributes_unplanned", [])
|
|
441
|
-
],
|
|
442
|
-
)
|
|
443
|
-
return table
|
|
435
|
+
return _build_report_table(
|
|
436
|
+
config, tasks, config.get("attributes_unplanned", ["id", "project", "description"]), theme
|
|
437
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskcheck
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Automatic scheduler for taskwarrior
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -114,6 +114,11 @@ Create a preview with `taskcheck --schedule --dry-run`. It does not update Taskw
|
|
|
114
114
|
saves the generated schedule to the Taskwarrior data directory. Reuse it with `taskcheck --report
|
|
115
115
|
today --dry-run` or `taskcheck --timeline 1w --dry-run`.
|
|
116
116
|
|
|
117
|
+
Report columns use the labels from `uda.<attribute>.label` in the active Taskwarrior configuration.
|
|
118
|
+
Reports show a robot icon beside the task ID for tasks tagged `agent`.
|
|
119
|
+
Dry-run schedules retain custom task attributes, including `agent_weight`.
|
|
120
|
+
When you load an older preview, Taskcheck fills missing task attributes from Taskwarrior and keeps the saved schedule.
|
|
121
|
+
|
|
117
122
|
Timeline rows follow dotted project levels such as `work.client`. Without `--zoom`, the most
|
|
118
123
|
detailed view that fits the terminal width is selected: day, then week, then month. The track uses
|
|
119
124
|
`▶` for a start, `━` for work, `◀` for an end, and `◆` for a deadline. Thin, heavy, and double rules
|
|
@@ -22,11 +22,13 @@ taskcheck.egg-info/top_level.txt
|
|
|
22
22
|
tests/test_agent_blocks.py
|
|
23
23
|
tests/test_agent_scheduler_integration.py
|
|
24
24
|
tests/test_common.py
|
|
25
|
+
tests/test_dry_run.py
|
|
25
26
|
tests/test_google_calendar.py
|
|
26
27
|
tests/test_ical.py
|
|
27
28
|
tests/test_install.py
|
|
28
29
|
tests/test_main.py
|
|
29
30
|
tests/test_parallel.py
|
|
30
31
|
tests/test_report.py
|
|
32
|
+
tests/test_report_columns.py
|
|
31
33
|
tests/test_timeline.py
|
|
32
34
|
tests/test_undo.py
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from unittest.mock import patch
|
|
3
|
+
|
|
4
|
+
from taskcheck.dry_run import load_dry_run_results
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_old_preview_recovers_missing_attributes_without_changing_schedule(tmp_path):
|
|
8
|
+
path = tmp_path / "dry-run.json"
|
|
9
|
+
cached = [{"uuid": "agent", "scheduling": "2026-11-10 - PT2H"}]
|
|
10
|
+
path.write_text(json.dumps(cached))
|
|
11
|
+
current = [{"uuid": "agent", "agent_weight": 25,
|
|
12
|
+
"scheduling": "2026-11-11 - PT1H"}]
|
|
13
|
+
with patch("taskcheck.dry_run.get_dry_run_path", return_value=path), patch(
|
|
14
|
+
"taskcheck.dry_run.subprocess.run"
|
|
15
|
+
) as export:
|
|
16
|
+
export.return_value.stdout = json.dumps(current)
|
|
17
|
+
result = load_dry_run_results()
|
|
18
|
+
assert result is not None
|
|
19
|
+
assert result[0]["agent_weight"] == 25
|
|
20
|
+
assert result[0]["scheduling"] == "2026-11-10 - PT2H"
|
|
21
|
+
assert json.loads(path.read_text()) == cached
|
|
@@ -246,7 +246,7 @@ class TestReportGeneration:
|
|
|
246
246
|
@patch('taskcheck.report.fetch_tasks')
|
|
247
247
|
@patch('taskcheck.report.get_days_in_constraint')
|
|
248
248
|
@patch('taskcheck.report.get_unplanned_tasks')
|
|
249
|
-
def test_generate_report_with_unplanned(self, mock_unplanned, mock_days, mock_fetch, sample_config, test_taskrc):
|
|
249
|
+
def test_generate_report_with_unplanned(self, mock_unplanned, mock_days, mock_fetch, sample_config, test_taskrc, capsys):
|
|
250
250
|
mock_fetch.return_value = []
|
|
251
251
|
mock_days.return_value = [(2023, 12, 5)]
|
|
252
252
|
mock_unplanned.return_value = [
|
|
@@ -260,7 +260,7 @@ class TestReportGeneration:
|
|
|
260
260
|
|
|
261
261
|
generate_report(sample_config, "today", verbose=True, taskrc=test_taskrc)
|
|
262
262
|
|
|
263
|
-
|
|
263
|
+
assert "Unplanned task" in capsys.readouterr().out
|
|
264
264
|
|
|
265
265
|
@patch('taskcheck.report.display_unplanned_tasks')
|
|
266
266
|
@patch('taskcheck.report.get_unplanned_tasks')
|
|
@@ -278,5 +278,4 @@ class TestReportGeneration:
|
|
|
278
278
|
generate_report(sample_config, "today", verbose=False, taskrc=test_taskrc, scheduling_results=scheduling_results)
|
|
279
279
|
|
|
280
280
|
mock_fetch.assert_called_once_with(test_taskrc)
|
|
281
|
-
mock_unplanned.assert_called_once_with(sample_config["report"], mock_fetch.return_value, taskrc=test_taskrc)
|
|
282
281
|
mock_display.assert_called_once()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from io import StringIO
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
from taskcheck.report import build_tasks_table, build_unplanned_tasks_table, get_tasks
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_explicit_columns_replace_defaults_in_order():
|
|
9
|
+
task = {"id": 2, "description": "Agent work", "urgency": 1,
|
|
10
|
+
"tags": ["agent"], "agent_weight": 0.34,
|
|
11
|
+
"scheduling": "2026-11-10 - PT2H"}
|
|
12
|
+
config = {"attributes": ["agent_weight", "description"],
|
|
13
|
+
"attribute_labels": {"agent_weight": "Attention"}}
|
|
14
|
+
rows = get_tasks(config, [task], 2026, 11, 10)
|
|
15
|
+
table = build_tasks_table(config, rows)
|
|
16
|
+
assert [column.header for column in table.columns] == ["Attention", "Description"]
|
|
17
|
+
stream = StringIO()
|
|
18
|
+
Console(file=stream, width=100).print(table)
|
|
19
|
+
assert "0.34" in stream.getvalue()
|
|
20
|
+
assert "Agent work" in stream.getvalue()
|
|
21
|
+
assert "#2" not in stream.getvalue()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_unplanned_columns_are_independent_and_empty_means_none():
|
|
25
|
+
config = {"attributes": [], "attributes_unplanned": ["description"]}
|
|
26
|
+
tasks = [{"id": 1, "description": "Unplanned work"}]
|
|
27
|
+
assert build_tasks_table(config, tasks).columns == []
|
|
28
|
+
table = build_unplanned_tasks_table(config, tasks)
|
|
29
|
+
assert [column.header for column in table.columns] == ["Description"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|