WikiFree Editorial

Free Python Programming Learning Path for Beginners

Learn Python with free Microsoft lessons and build a tested CSV command-line tool. Includes practice tasks, reference code and edge-case tests.

Free Python Programming Learning Path for Beginners. The maker’s terminal: Read. Validate. Summarize. Make behavior testable.
HomeArticlesThe maker’s terminal

WikiFree learning paths / 05 · The maker’s terminal

The maker’s terminal

Build a small tool. Make its behavior predictable.

Create a command-line program that validates a ticket CSV, summarizes workload and passes tests for normal, empty and invalid input.

Build log / 001

Make behavior testable.

$ python ticket_summary.py sample_tickets.csv

Tickets: 6
Total minutes: 105
Open tickets: 3

Input → Validate → Summarize
Actual output of the included reference on the synthetic sample.
Beginner
starting level
1
core course
≈18h
learning + practice estimate
5 + 1
stages + final project

The practical outcome

What you will be able to show

Create a command-line program that validates a ticket CSV, summarizes workload and passes tests for normal, empty and invalid input.

For: First-time programmers comfortable managing files and folders.

Before you begin: A computer, Python 3 and a text editor. The reference uses only the standard library; no paid API, cloud account or package subscription is required.

Original pathway and exercises by WikiFree Editorial. Course pages and official source metadata reviewed on 12 October 2026. Version 1.0.

A ten-minute starting check

Try these before opening a lesson

Answer in your own words, then reveal the guidance. If you can already demonstrate a stage’s completion gate with evidence, skim its lesson assignment and move to the next stage.

Can you find a file from a terminal using its path?

If not, begin with setup and practice opening the project folder before writing logic.

Why can the text "12" behave differently from the number 12?

CSV fields arrive as strings; explicit conversion is needed for arithmetic.

What should happen when an input file is missing?

The program should report a clear error and return a nonzero exit code, not invent an empty successful report.

Sequence with a purpose

Build one layer at a time

  1. 01Run and observeA runnable script and environment note.
  2. 02Model valid and invalid valuesA validation table and parsing functions.
  3. 03Aggregate a collectionA pure summary function and expected-results table.
  4. 04Read CSV and expose a clear commandA CLI with help text and readable failures.
  5. 05Prove behavior with automated testsAn automated suite and a short test report.

Finish with the capstone. Progress means a reviewable output, not simply a watched video.

A deliberately short course list

Use these courses for these reasons

Core

Free Python Course for Beginners by Microsoft

Microsoft · Beginner · English

Short paired explanations and demos support an incremental first program. We stop before APIs, keys and decorators because they do not help this project.

Assigned viewing: 2h 3m 49s. The full source course contains additional material.

Open the WikiFree course and official learning route ↗

Free learning, clearly defined

The selected Microsoft YouTube videos are free to view. Python and a plain editor are sufficient for the project. Setup screens in older videos may differ from current software. No certificate is promised.

Skills and evidence

The assessed route and its outputs
Stage / skillLearning supportEvidence to produce
01 · Run and observeMicrosoft selected lessonsA runnable script and environment note.
02 · Model valid and invalid valuesMicrosoft selected lessonsA validation table and parsing functions.
03 · Aggregate a collectionMicrosoft selected lessonsA pure summary function and expected-results table.
04 · Read CSV and expose a clear commandWikiFree original bridge lessonA CLI with help text and readable failures.
05 · Prove behavior with automated testsWikiFree original bridge lessonAn automated suite and a short test report.

Stage 1 / The maker’s terminal

Run and observe

You will: Execute a script and explain its output.

Assigned learning

Free Python Course for Beginners by Microsoft

  • Lesson 1 · Programming with Python
  • Lesson 2 · Introducing Python
  • Lesson 3 · Getting Started
  • Lesson 4 · Configuring Visual Studio Code
  • Lesson 5 · Using Print
  • Lesson 6 · Demo: Hello World

Use the names if the provider later changes the order. Only these assignments enter the core estimate.

Create a project folder and save a small script before experimenting. Run it from that folder with python ticket_summary.py or the Python 3 launcher used on your system. A filename and its contents are different things; the terminal must point to the file you actually edited.

Write a short output contract before implementing it: summary labels, supported inputs and what failure looks like. That contract will later become test cases.

Your practice assignment

Make something reviewable

Print a heading and three labeled values for a fictional ticket. Change one value and predict the new output before rerunning. Record the Python version that actually ran.

Deliver: A runnable script and environment note.

Completion gate

You can run the saved file twice and explain each output line.

If you get stuck: If “file not found” appears, inspect the working folder and filename. If the old output persists, save the editor buffer and rerun the intended file.

Stage 2 / The maker’s terminal

Model valid and invalid values

You will: Convert and validate data deliberately.

Assigned learning

Free Python Course for Beginners by Microsoft

  • Lesson 9 · String Concepts
  • Lesson 10 · Demo: Strings
  • Lesson 13 · Numeric Data Types
  • Lesson 14 · Demo: Numbers
  • Lesson 17 · Error Handling
  • Lesson 18 · Demo: Error Handling
  • Lesson 19 · Conditional Logic
  • Lesson 20 · Demo: Conditional Logic

Use the names if the provider later changes the order. Only these assignments enter the core estimate.

A CSV value such as “15” starts as text. Convert it once, check the allowed range, and keep validation separate from reporting. Zero minutes is valid; a negative number, decimal fraction or blank is invalid under this project’s rulebook.

Use exceptions for failed input rather than silently substituting zero. A useful error names the row and field. Avoid a broad exception handler that hides programming mistakes behind “something went wrong.”

Your practice assignment

Make something reviewable

Write a parse_minutes function. Demonstrate inputs "15", "0", "-1", "", "2.5" and "hello". Add allowed-team and allowed-status checks.

Deliver: A validation table and parsing functions.

Completion gate

The first two minute examples pass; the other four fail clearly; invalid values never enter the totals.

If you get stuck: If an empty string becomes zero, remove the default. If a valid zero is rejected, check whether you used a truthiness test instead of an explicit range check.

Stage 3 / The maker’s terminal

Aggregate a collection

You will: Summarize rows with loops and functions.

Assigned learning

Free Python Course for Beginners by Microsoft

  • Lesson 25 · Collections
  • Lesson 26 · Demo: Collections
  • Lesson 27 · Loops
  • Lesson 28 · Demo: Loops
  • Lesson 29 · Introducing Functions
  • Lesson 30 · Demo: Functions
  • Lesson 31 · Parameterized Functions
  • Lesson 32 · Demo: Parameterized functions

Use the names if the provider later changes the order. Only these assignments enter the core estimate.

A list holds ticket records; a dictionary groups counts by team. Keep summarize(rows) independent from terminal printing so you can call it directly in a test. Start accumulators once, outside the loop. Update each ticket exactly once.

Separate validation from aggregation. If a duplicate ticket ID appears, this project rejects the file. That policy prevents accidental double counting; a different domain might need another documented rule.

Your practice assignment

Make something reviewable

Summarize three hard-coded tickets across two teams. Add a fourth and predict the ticket count, minute total and team change. Then refactor repeated work into named functions.

Deliver: A pure summary function and expected-results table.

Completion gate

Totals match a hand calculation and duplicate IDs are rejected instead of counted twice.

If you get stuck: If only the last ticket survives, move accumulator initialization outside the loop. If team totals leak between calls, create a fresh dictionary inside the function.

Stage 4 / The maker’s terminal

Read CSV and expose a clear command

You will: Connect file input without weakening validation.

Original WikiFree bridge lesson

Use csv.DictReader instead of splitting each line on commas; a quoted note can contain a comma. Open text files with an explicit encoding and newline="". Validate required headers before processing rows.

Use argparse for the input path. Keep main() responsible for file access, friendly errors and exit codes. A header-only file is a valid empty dataset; a file without the required header is invalid. These are different cases.

Your practice assignment

Make something reviewable

Implement <code>python ticket_summary.py sample_tickets.csv</code>. Try a missing file, a header-only file and a note containing a quoted comma. Use the supplied contract rather than guessing how to handle them.

Deliver: A CLI with help text and readable failures.

Completion gate

The sample prints the expected summary; a missing file exits nonzero; quoted notes do not shift columns.

If you get stuck: If commas break rows, use the CSV parser. If invalid input prints a success-looking partial total, validate the whole file before rendering the summary.

Stage 5 / The maker’s terminal

Prove behavior with automated tests

You will: Cover boundaries and failure contracts.

Original WikiFree bridge lesson

Write small tests with unittest: normal data, zero minutes, empty valid dataset, missing header, duplicate ID, unknown team, negative or non-integer minutes and a missing file. Assert outputs or raised errors that matter to a user.

A test is useful when a plausible mistake makes it fail. Changing a negative-minute check should break the relevant test. Do not rely only on a test that the module imports successfully.

Your practice assignment

Make something reviewable

Run <code>python -m unittest -v</code>. Deliberately break the duplicate-ID validation in a copy, observe the failing test, then restore it. Capture actual results and the interpreter version.

Deliver: An automated suite and a short test report.

Completion gate

Normal and failure cases pass after restoration; at least one deliberate defect is detected.

If you get stuck: If a test never fails, check that it calls the relevant function and asserts an observable result. Keep temporary files isolated from your real input data.

Original practice material

Your case pack and reference checks

Download the editable plain-text workbook, or open the full copy below. It contains the case inputs, templates and reference guidance. Try the exercises before reading the answers.

Download the practice workbook ↓ Download project files (ZIP) ↓
Open the complete workbook in this page
WIKIFREE / THE MAKER'S TERMINAL
Original synthetic case and reference solution. 12 October 2026.

INPUT CONTRACT
Required header: ticket_id,team,minutes,status,note. No additional columns.
IDs must be nonblank and unique. Team: Support, Billing or Operations.
Status: open or closed. Minutes: nonnegative integer; blank is invalid.
Whitespace around IDs, team and status is trimmed. Notes may contain commas
when quoted correctly. Header-only is a valid empty dataset. No header is invalid.
Validate the whole file before printing a success summary. Errors exit with code 2.

RUN
Save the three code/data sections below using their indicated filenames.
python ticket_summary.py sample_tickets.csv
python -m unittest -v test_ticket_summary.py
Use the Python 3 launcher appropriate to your system. Only standard library needed.
Expected sample: 6 tickets, 105 minutes, 3 open. Billing 2/30, Operations 1/35,
Support 3/40 (tickets/minutes). These are synthetic workload data.

BEFORE THE REFERENCE
Build parse_minutes, then summary logic, then CSV reading, then the CLI.
Keep a prediction/observation test log. Do not copy the solution before trying.
Document your actual Python version and the cases you really executed.


===== sample_tickets.csv =====
ticket_id,team,minutes,status,note
T01,Support,15,closed,"Question, then resolved"
T02,Billing,20,open,Awaiting clarification
T03,Support,0,open,No work recorded yet
T04,Operations,35,closed,Setup complete
T05,Billing,10,closed,Invoice explained
T06,Support,25,open,Follow-up needed


===== ticket_summary.py =====
"""WikiFree training reference: validate an entire CSV before printing a summary."""
import argparse
import csv
import sys

TEAMS = {"Support", "Billing", "Operations"}
STATUSES = {"open", "closed"}
REQUIRED = {"ticket_id", "team", "minutes", "status", "note"}


def parse_minutes(value, row_number):
    if value is None or not value.strip():
        raise ValueError("row {}: minutes is required".format(row_number))
    try:
        minutes = int(value)
    except ValueError:
        raise ValueError("row {}: minutes must be an integer".format(row_number))
    if minutes < 0:
        raise ValueError("row {}: minutes cannot be negative".format(row_number))
    return minutes


def read_tickets(stream):
    reader = csv.DictReader(stream)
    if reader.fieldnames is None or set(reader.fieldnames) != REQUIRED:
        raise ValueError("header must contain ticket_id, team, minutes, status and note")
    if len(reader.fieldnames) != len(REQUIRED):
        raise ValueError("header contains duplicate columns")
    rows, seen = [], set()
    for line_number, raw in enumerate(reader, 2):
        if None in raw or any(value is None for value in raw.values()):
            raise ValueError("row {}: wrong number of fields".format(line_number))
        ticket_id = raw["ticket_id"].strip()
        if not ticket_id or ticket_id in seen:
            raise ValueError("row {}: missing or duplicate ticket_id".format(line_number))
        team = raw["team"].strip()
        status = raw["status"].strip()
        if team not in TEAMS:
            raise ValueError("row {}: unknown team {!r}".format(line_number, team))
        if status not in STATUSES:
            raise ValueError("row {}: unknown status {!r}".format(line_number, status))
        minutes = parse_minutes(raw["minutes"], line_number)
        seen.add(ticket_id)
        rows.append({"ticket_id": ticket_id, "team": team, "minutes": minutes,
                     "status": status, "note": raw["note"]})
    return rows


def summarize(rows):
    result = {"tickets": 0, "minutes": 0, "open": 0, "teams": {}}
    for row in rows:
        result["tickets"] += 1
        result["minutes"] += row["minutes"]
        result["open"] += int(row["status"] == "open")
        team = result["teams"].setdefault(row["team"], {"tickets": 0, "minutes": 0})
        team["tickets"] += 1
        team["minutes"] += row["minutes"]
    return result


def format_summary(result):
    lines = ["Tickets: {}".format(result["tickets"]),
             "Total minutes: {}".format(result["minutes"]),
             "Open tickets: {}".format(result["open"])]
    for name, team in sorted(result["teams"].items()):
        lines.append("{}: {} tickets, {} minutes".format(name, team["tickets"], team["minutes"]))
    return "\n".join(lines)


def main(argv=None):
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("input_csv", help="UTF-8 CSV with the documented five-column header")
    args = parser.parse_args(argv)
    try:
        with open(args.input_csv, encoding="utf-8-sig", newline="") as stream:
            rows = read_tickets(stream)
        result = summarize(rows)
    except (OSError, UnicodeError, ValueError, csv.Error) as error:
        print("Error: {}".format(error), file=sys.stderr)
        return 2
    print(format_summary(result))
    return 0


if __name__ == "__main__":
    sys.exit(main())


===== test_ticket_summary.py =====
import contextlib
import io
import pathlib
import tempfile
import unittest
from ticket_summary import read_tickets, summarize, parse_minutes, main

HEADER = "ticket_id,team,minutes,status,note\n"


class TicketSummaryTests(unittest.TestCase):
    def parse(self, body):
        return read_tickets(io.StringIO(HEADER + body))

    def test_normal_totals_and_quoted_comma(self):
        rows = self.parse('A,Support,15,open,"Question, then follow-up"\nB,Billing,5,closed,Done\nC,Support,0,open,Waiting\n')
        self.assertEqual(summarize(rows), {"tickets": 3, "minutes": 20, "open": 2,
                                          "teams": {"Support": {"tickets": 2, "minutes": 15},
                                                    "Billing": {"tickets": 1, "minutes": 5}}})
        self.assertEqual(rows[0]["note"], "Question, then follow-up")

    def test_zero_is_valid(self):
        self.assertEqual(parse_minutes("0", 2), 0)

    def test_invalid_minutes(self):
        for value in ("-1", "", "2.5", "hello", None):
            with self.subTest(value=value), self.assertRaises(ValueError):
                parse_minutes(value, 2)

    def test_header_only_is_valid_empty_dataset(self):
        self.assertEqual(summarize(self.parse("")), {"tickets": 0, "minutes": 0, "open": 0, "teams": {}})

    def test_missing_header(self):
        with self.assertRaisesRegex(ValueError, "header"):
            read_tickets(io.StringIO(""))

    def test_duplicate_id(self):
        with self.assertRaisesRegex(ValueError, "duplicate"):
            self.parse("A,Support,1,open,x\nA,Support,2,closed,y\n")

    def test_duplicate_header(self):
        with self.assertRaisesRegex(ValueError, "duplicate columns"):
            read_tickets(io.StringIO(HEADER.strip() + ",note\n"))

    def test_unknown_team_and_status(self):
        for row in ("A,Unknown,1,open,x\n", "A,Support,1,maybe,x\n"):
            with self.subTest(row=row), self.assertRaises(ValueError):
                self.parse(row)

    def test_wrong_field_count(self):
        for row in ("A,Support,1,open\n", "A,Support,1,open,x,extra\n"):
            with self.subTest(row=row), self.assertRaisesRegex(ValueError, "fields"):
                self.parse(row)

    def test_missing_file_nonzero_and_no_success_output(self):
        with tempfile.TemporaryDirectory() as folder:
            out, err = io.StringIO(), io.StringIO()
            with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
                code = main([str(pathlib.Path(folder) / "absent.csv")])
            self.assertEqual(code, 2)
            self.assertEqual(out.getvalue(), "")
            self.assertIn("Error:", err.getvalue())

    def test_late_bad_row_does_not_print_partial_success(self):
        with tempfile.TemporaryDirectory() as folder:
            path = pathlib.Path(folder) / "tickets.csv"
            path.write_text(HEADER + "A,Support,10,open,Valid\nB,Support,-1,open,Invalid\n")
            out, err = io.StringIO(), io.StringIO()
            with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
                code = main([str(path)])
            self.assertEqual(code, 2)
            self.assertEqual(out.getvalue(), "")
            self.assertIn("negative", err.getvalue())


if __name__ == "__main__":
    unittest.main()

Case data, exercises and reference examples: WikiFree Editorial, original training material. Provider lessons are linked, not reproduced.

Final project / 4h practice estimate

A dependable help-desk summary tool

Deliver a small standard-library program another learner can run from the terminal. Match the documented input contract and explain what happens for each invalid case.

Your handover package

  • ticket_summary.py with separated parsing, validation, aggregation and CLI code
  • Synthetic sample CSV and a README with commands and assumptions
  • Automated tests for valid, empty, duplicate and invalid inputs
  • A test report with actual Python version and one demonstrated caught defect
Capstone rubric · 100 points
CriterionPointsEvidence expected
Behavior and totals30The sample and independent cases produce correct totals.
Validation and failures30Bad headers, duplicates and invalid values fail clearly without partial success.
Design and readability20Functions have clear responsibilities and no unnecessary dependencies.
Tests and handover20Tests cover meaningful boundaries and documented commands work.

Completion standard: aim for at least 80/100, with all critical gates met. A reviewer awards each row’s points in proportion to the evidence shown. This is a learning rubric, not a provider credential.

Silently accepting invalid data or double counting a duplicate blocks completion.

A pace that leaves room to practice

Plan the next few weeks

Assigned viewing: 2h 3m 49s. Practice and capstone: 15.0 hours, editorial estimate. Budget about 18 hours overall. Replays and personal adaptations can take longer.

Pace examples · all include the final project
Available timeFlexible estimateHow to use it
2 hours/week9 weeksReserve time to make the output and pass its gate; split a long lesson across sessions.
5 hours/week4 weeksReserve time to make the output and pass its gate; split a long lesson across sessions.
10 hours/week2 weeksReserve time to make the output and pass its gate; split a long lesson across sessions.
About 4 weeks at 5 hours per week.

The calculation stays in your browser. The estimates above remain usable without JavaScript.

Your evidence checklist0 of 6 completion gates checked

Checkbox progress is saved only in this browser when local storage is available. It is not sent to WikiFree or synchronized to an account.

Questions before you continue

Should I install a large data library?

No. This project is intentionally small enough for csv, argparse and unittest. Learn the input contract before adding dependencies.

Can I open the reference solution immediately?

Try each stage first. Compare behavior and design afterward; matching the reference character for character is not the goal.

Sources and honest boundaries

What was checked, and what comes next

The Microsoft course supplies language foundations. CSV validation, command-line design and automated tests below are original WikiFree bridge lessons, supported by Python documentation. This route does not cover web frameworks or data science.

The complete published WikiFree course catalog was checked on 12 October 2026: 815 courses across nine API pages. Course pages and official playlist metadata or module headings were inspected. This does not mean every video was played to completion, every geographic region was tested, or a provider credential was earned.

Keep the published course pages as your route into provider material. Access, lesson order and tool interfaces can change. Report a broken route through WikiFree’s correction page.

Connect this project to another skill: Project Management · AI & ChatGPT for Work.

WikiFree Editorial · Independent learning guidance · Reviewed 12 October 2026 · No affiliation, job outcome or certificate is implied.

Share this article

Questions & discussion

Share a useful question or correction. Comments appear after moderation. Please avoid personal or sensitive information.

Add to the discussion

Your email address will not be published. Required fields are marked *