Metadata-Version: 2.4
Name: tasker-hacha
Version: 0.1.1
Summary: A command-line task tracker built to learn modern Python packaging.
Author: B.Hachem
License: MIT License
        
        Copyright (c) 2026 Hachem Brahimi
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=6.0; extra == "dev"
Dynamic: license-file

# Tasker

Tasker is a simple command-line task tracker built as a learning project for understanding modern Python project structure, packaging, testing, and deployment.

The project was built incrementally to explore:

- Python virtual environments
- Modern Python packaging with `pyproject.toml`
- The `src` layout
- `setuptools`
- Editable installations
- Command-line interfaces with `argparse`
- Dataclasses
- JSON persistence
- Separation of business logic and storage
- Automated testing with `pytest`
- Package building
- GitHub Actions
- Trusted Publishing
- TestPyPI deployment

---

## Features

Tasker currently supports:

- Adding tasks
- Listing tasks
- Marking tasks as completed
- Removing tasks
- Clearing all tasks
- Persistent JSON storage
- Automatic task ID generation
- Command-line argument parsing with `argparse`
- Automated tests
- Python package installation
- CI with GitHub Actions
- Publishing to TestPyPI

---

## Requirements

- Python 3.12 or newer
- `pip`

The project was developed and tested with Python 3.12.

---

# Installation

## Install from PyPI

Once the package is published to PyPI, it can be installed with:

```bash
pip install tasker
```

## Install from Source

Clone the repository:

```bash
git clone https://github.com/hacha007/tasker.git
```

Move into the project directory:

```bash
cd tasker
```

Create a virtual environment:

```powershell
py -3.12 -m venv .venv
```

Activate the virtual environment on Windows PowerShell:

```powershell
.\.venv\Scripts\Activate.ps1
```

Upgrade `pip`:

```powershell
python -m pip install --upgrade pip
```

Install the project in editable mode:

```powershell
pip install -e .
```

---

# Development Installation

For development, install the package together with its development dependencies:

```powershell
pip install -e ".[dev]"
```

The development dependencies currently include:

- `pytest`

Editable installation means that changes made to the source code are immediately reflected in the installed package without reinstalling it.

---

# Usage

Tasker is used through the command line.

The general structure is:

```text
python -m tasker <command>
```

To see the available commands:

```powershell
python -m tasker --help
```

---

## Add a Task

Add a new task with the `add` command:

```powershell
python -m tasker add "Buy milk"
```

The title is a positional argument.

Example output:

```text
Adding task: Buy milk
```

Tasker automatically generates a task ID.

---

## List Tasks

Display all stored tasks:

```powershell
python -m tasker list
```

Example:

```text
[ ] 1: Buy milk
[✓] 2: Learn pytest
```

The symbols represent the completion state:

- `[ ]` — incomplete
- `[✓]` — completed

If no tasks exist:

```text
No tasks found.
```

---

## Complete a Task

Mark a task as completed using its ID:

```powershell
python -m tasker done 1
```

For example:

```text
Marking task as done: task_1
```

The task's `completed` field is then changed from:

```json
false
```

to:

```json
true
```

---

## Remove a Task

Remove a task using its ID:

```powershell
python -m tasker remove 1
```

Example:

```text
Removing task: task_1
```

---

## Clear All Tasks

Remove all stored tasks:

```powershell
python -m tasker clear
```

Example:

```text
Clearing all tasks...
```

---

## Show Help

Display the available commands and arguments:

```powershell
python -m tasker --help
```

---

# Command Reference

| Command | Description |
|---|---|
| `tasker list` | List all tasks |
| `tasker add <title>` | Add a new task |
| `tasker done <id>` | Mark a task as completed |
| `tasker remove <id>` | Remove a task |
| `tasker clear` | Remove all tasks |
| `tasker --help` | Display help |

When using the Python module directly, the commands are executed as:

```text
python -m tasker list
python -m tasker add "Buy milk"
python -m tasker done 1
python -m tasker remove 1
python -m tasker clear
```

---

# Project Architecture

Tasker uses a layered structure where different parts of the application have different responsibilities.

```text
User
  │
  ▼
__main__.py
  │
  ▼
cli.py
  │
  ▼
commands.py
  │
  ▼
storage.py
  │
  ▼
tasks.json
```

## `__main__.py`

The package entry point.

It allows the application to be executed with:

```powershell
python -m tasker
```

Its responsibility is intentionally minimal: it calls the application's `main()` function.

---

## `cli.py`

Responsible for the command-line interface.

It handles:

- Creating the `ArgumentParser`
- Defining subcommands
- Defining command arguments
- Parsing command-line arguments
- Dispatching the selected command

For example:

```text
tasker add "Buy milk"
```

is parsed into information such as:

```text
command = "add"
title = "Buy milk"
```

The CLI then calls the appropriate function from `commands.py`.

---

## `commands.py`

Contains the application's business logic.

It implements operations such as:

- `list_tasks()`
- `add_task()`
- `done_task()`
- `remove_task()`
- `clear_tasks()`

This module does not need to know how command-line arguments were parsed.

That separation means the same business logic could later be reused by another interface, such as a web API.

---

## `models.py`

Contains the application's data model.

Tasker uses a Python dataclass to represent a task:

```python
@dataclass
class Task:
    task_id: int
    title: str
    completed: bool = False
```

A task therefore has three pieces of information:

- `task_id`
- `title`
- `completed`

The default value of `completed` is `False`.

---

## `storage.py`

Responsible for persistence.

It handles:

- Loading tasks from JSON
- Saving tasks to JSON
- Converting dictionaries into `Task` objects
- Converting `Task` objects into dictionaries

The business logic does not directly manipulate JSON files.

This separation makes it possible to replace JSON with another storage system later without having to rewrite the application's business logic.

---

# Data Storage

Tasker currently uses JSON for persistent storage.

Tasks are stored as a list of dictionaries.

Example:

```json
[
    {
        "task_id": 1,
        "title": "Buy milk",
        "completed": false
    },
    {
        "task_id": 2,
        "title": "Learn pytest",
        "completed": true
    }
]
```

The JSON file is not stored inside the Python package.

The package converts between Python objects and JSON data.

---

## Serialization

Serialization converts Python data into a format that can be stored.

For example:

```text
Task object
    ↓
asdict()
    ↓
dictionary
    ↓
json.dump()
    ↓
JSON file
```

The project uses `dataclasses.asdict()` to convert `Task` objects into dictionaries.

---

## Deserialization

Deserialization performs the opposite operation:

```text
JSON file
    ↓
json.load()
    ↓
dictionary
    ↓
Task(...)
    ↓
Task object
```

This allows the application to work with `Task` objects internally while using JSON for persistent storage.

---

# Task IDs

Task IDs are generated by finding the highest existing ID and adding one.

Conceptually:

```python
highest_id = max(
    (task.task_id for task in tasks),
    default=0,
)

new_task_id = highest_id + 1
```

This avoids using:

```python
len(tasks) + 1
```

because deleting a task could otherwise cause IDs to be reused.

For example, if the tasks are:

```text
1
2
3
```

and task `2` is removed, the next task should receive:

```text
4
```

rather than:

```text
3
```

---

# Project Structure

The project uses the `src` layout:

```text
tasker/
│
├── .github/
│   └── workflows/
│       ├── tests.yml
│       └── publish.yml
│
├── src/
│   └── tasker/
│       ├── __init__.py
│       ├── __main__.py
│       ├── cli.py
│       ├── commands.py
│       ├── models.py
│       └── storage.py
│
├── tests/
│   ├── test_models.py
│   └── test_storage.py
│
├── .gitignore
├── LICENSE
├── README.md
└── pyproject.toml
```

Build artifacts and development files such as `build/`, `dist/`, `*.egg-info/`, `.pytest_cache/`, IDE configuration, and operating-system files are excluded through `.gitignore`.

---

# Why the `src` Layout?

The project uses:

```text
src/
└── tasker/
```

instead of placing the package directly in the project root.

The `src` layout helps prevent accidental imports from the source directory.

It makes the development environment more closely resemble how the package behaves after installation.

The package therefore needs to be installed before it can be imported normally.

---

# Packaging

Tasker uses modern Python packaging through `pyproject.toml`.

The project uses `setuptools` as its build backend.

The build system is configured with:

```toml
[build-system]
requires = ["setuptools>=77.0"]
build-backend = "setuptools.build_meta"
```

The project metadata is defined using the `[project]` table.

The package currently has version:

```text
0.1.0
```

---

# Editable Installation

During development, Tasker is installed using:

```powershell
pip install -e .
```

The `-e` means editable installation.

Instead of copying the source code into the environment, the installation references the development source directory.

This means changes to the source code are immediately reflected when running the installed package.

---

# Testing

Tasker uses `pytest` for automated testing.

Install the development dependencies:

```powershell
pip install -e ".[dev]"
```

Run the tests:

```powershell
pytest
```

The test suite currently covers:

- Task dataclass behavior
- Default completion state
- Task ID storage
- Task title storage
- Saving tasks
- Loading tasks
- JSON serialization
- JSON deserialization

Temporary files are created using pytest's `tmp_path` fixture so that tests do not modify the application's real task storage.

---

# Building the Package

The package can be built into standard Python distribution formats.

Install the build tool:

```powershell
python -m pip install build
```

Build the package:

```powershell
python -m build
```

This creates the `dist/` directory:

```text
dist/
├── tasker-0.1.0-py3-none-any.whl
└── tasker-0.1.0.tar.gz
```

The wheel is the built package distribution used by `pip`.

The source distribution contains the source files needed to build the package.

Build artifacts are not committed to the repository.

---

# Continuous Integration

The project uses GitHub Actions for continuous integration.

The test workflow runs when changes are pushed or when a pull request is opened.

The workflow:

1. Checks out the repository.
2. Sets up Python 3.12.
3. Upgrades `pip`.
4. Installs the package and development dependencies.
5. Runs the test suite with `pytest`.

This ensures that the automated tests are executed in a clean environment rather than relying only on local testing.

---

# Publishing

The project uses GitHub Actions to publish releases.

The publishing workflow is triggered by version tags such as:

```text
v0.1.0
```

The release pipeline performs the following steps:

```text
Git tag
   ↓
GitHub Actions
   ↓
Checkout repository
   ↓
Set up Python
   ↓
Install dependencies
   ↓
Run tests
   ↓
Build package
   ↓
Trusted Publishing
   ↓
TestPyPI
```

The project uses GitHub Actions OIDC Trusted Publishing rather than storing a PyPI API token in GitHub Secrets.

The first release was published to TestPyPI as:

```text
tasker 0.1.0
```

The package was then installed from TestPyPI in a separate virtual environment to verify that the built distribution could be installed and executed independently from the source repository.

---

# Release Versioning

The project follows semantic versioning concepts.

A version has three components:

```text
MAJOR.MINOR.PATCH
```

For example:

```text
0.1.0
```

Generally:

- `PATCH` — bug fixes and small backwards-compatible changes
- `MINOR` — new backwards-compatible functionality
- `MAJOR` — incompatible API changes

Git tags use a `v` prefix:

```text
v0.1.0
```

while the package version itself is:

```text
0.1.0
```

---

# Learning Goals

This project is intentionally small.

The purpose is not to build a production task-management application, but to understand the foundations behind a real Python package.

The project covers the complete path from source code to an installable package:

```text
Python source
      ↓
Project structure
      ↓
pyproject.toml
      ↓
Package
      ↓
Tests
      ↓
Build
      ↓
GitHub Actions
      ↓
TestPyPI
      ↓
pip installation
```

---

# Future Improvements

Possible future improvements include:

- Better error handling for missing or invalid task IDs
- More comprehensive CLI tests
- Improved command output
- Additional storage backends
- Database persistence
- A proper console-script entry point
- Improved packaging metadata
- Production PyPI publishing
- More comprehensive documentation

---

# License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
