Skip to content

Commit 1de38ee

Browse files
authored
Merge pull request #2 from atlasia-ma/chore/dev-tooling
chore: modernise packaging, add CI, fix unconstrained rendering
2 parents 0c9d395 + 9867dc8 commit 1de38ee

78 files changed

Lines changed: 1696 additions & 1221 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
concurrency:
10+
group: ci-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
lint:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
- uses: actions/setup-python@v5
19+
with:
20+
python-version: "3.12"
21+
cache: pip
22+
- run: pip install ruff
23+
- run: ruff check src tests
24+
- run: ruff format --check src tests
25+
26+
test:
27+
runs-on: ${{ matrix.os }}
28+
strategy:
29+
fail-fast: false
30+
matrix:
31+
os: [ubuntu-latest, windows-latest]
32+
python-version: ["3.10", "3.12"]
33+
steps:
34+
- uses: actions/checkout@v4
35+
- uses: actions/setup-python@v5
36+
with:
37+
python-version: ${{ matrix.python-version }}
38+
cache: pip
39+
- name: Install
40+
run: pip install -e ".[data,dev]"
41+
- name: Test
42+
run: pytest --cov --cov-report=term-missing

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here. The format follows
4+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to
5+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
### Added
10+
11+
- Development tooling: ruff lint/format configuration, pytest configuration with
12+
`pythonpath = ["src"]` (so a clean clone is testable without installing), coverage
13+
settings, and a GitHub Actions CI matrix over Linux/Windows and Python 3.10/3.12.
14+
- `CONTRIBUTING.md` describing the TDD workflow, branch naming, and release process.
15+
- Regression tests covering rendering without layout constraints.
16+
17+
### Changed
18+
19+
- `pyproject.toml`: dependency pins relaxed to compatible ranges, heavy tabular and
20+
Hugging Face dependencies moved into the `data` extra, project metadata and URLs
21+
completed.
22+
- `HuggingFaceTextLoader` accepts any iterable of mappings, streams records via
23+
`iter_texts`, and defers the `datasets` import to call time.
24+
25+
### Fixed
26+
27+
- `wrap_text_by_pixels` raised `TypeError` when `layout.max_width` / `layout.max_height`
28+
were unset, which made every sample fail for configs that omit them.
29+
- Whitespace-only input no longer produces a zero-sized canvas.

CONTRIBUTING.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Contributing to OCRSmith
2+
3+
Thanks for helping build OCRSmith. This document describes how the project is
4+
developed so that contributions land smoothly.
5+
6+
## Development setup
7+
8+
```bash
9+
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
10+
pip install -e ".[data,dev]"
11+
pytest
12+
```
13+
14+
`pytest` works from a clean clone without an install because `pythonpath = ["src"]`
15+
is set in `pyproject.toml`.
16+
17+
## Test-driven workflow
18+
19+
Every behavioural change starts with a test.
20+
21+
1. Write a failing test under `tests/` that expresses the desired behaviour.
22+
2. Run it and watch it fail for the right reason.
23+
3. Write the smallest implementation that makes it pass.
24+
4. Refactor with the suite green.
25+
26+
Rendering code is tested against *properties*, not golden images: bounding boxes
27+
must sit inside the page, text must round-trip through shaping, generated
28+
samples must carry a label. Golden-image tests are brittle across FreeType
29+
versions and are avoided.
30+
31+
## Code style
32+
33+
- `ruff check src tests` and `ruff format src tests` must pass.
34+
- Strategy classes live one-per-file under the subsystem they belong to and are
35+
re-exported from the package `__init__.py`.
36+
- Public functions carry type hints. Domain objects are frozen dataclasses.
37+
- Prefer generators over materialised lists on any path that can see a large
38+
dataset.
39+
40+
## Branch and commit conventions
41+
42+
Branches are named `<type>/<slug>`:
43+
44+
| Type | Use |
45+
| ---------- | ------------------------------------------ |
46+
| `feat/` | new capability |
47+
| `fix/` | bug fix |
48+
| `refactor/`| behaviour-preserving restructure |
49+
| `perf/` | performance work |
50+
| `docs/` | documentation only |
51+
| `chore/` | tooling, CI, dependencies |
52+
53+
Commits follow [Conventional Commits](https://www.conventionalcommits.org/):
54+
`feat(text): add bidi-aware line segmentation`.
55+
56+
## Pull requests
57+
58+
One PR per branch, one concern per PR. The description states what changed, why,
59+
and how it was verified. CI (lint + tests on Linux/Windows, Python 3.10 and 3.12)
60+
must be green before merge.
61+
62+
## Releasing
63+
64+
1. Update `CHANGELOG.md` under a new version heading.
65+
2. Bump `version` in `pyproject.toml`.
66+
3. Tag `vX.Y.Z` on `main` and publish the GitHub release.

pyproject.toml

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,111 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "ocrsmith"
77
version = "0.1.0"
8-
description = "A versatile OCR dataset generator for Arabic and Latin text, with synthetic text generation and dataset export."
8+
description = "Synthetic document and OCR dataset forge for Arabic, Darija and Latin scripts."
99
readme = "README.md"
1010
authors = [
1111
{ name = "Haitam Bouanane", email = "bouananehaitam03@gmail.com" }
1212
]
1313
license = { file = "LICENSE" }
14-
keywords = ["OCR", "synthetic data", "Arabic OCR", "dataset generator"]
15-
requires-python = ">=3.11"
14+
keywords = [
15+
"OCR",
16+
"synthetic data",
17+
"Arabic OCR",
18+
"document AI",
19+
"dataset generator",
20+
"document understanding",
21+
]
22+
classifiers = [
23+
"Development Status :: 4 - Beta",
24+
"Intended Audience :: Science/Research",
25+
"License :: OSI Approved :: MIT License",
26+
"Programming Language :: Python :: 3.10",
27+
"Programming Language :: Python :: 3.11",
28+
"Programming Language :: Python :: 3.12",
29+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
30+
"Topic :: Scientific/Engineering :: Image Processing",
31+
]
32+
requires-python = ">=3.10"
1633
dependencies = [
17-
"pydantic>=2.11.7",
18-
"PyYAML==6.0.2",
19-
"pillow==11.3.0",
20-
"numpy==2.3.1",
21-
"pandas==2.3.1",
22-
"datasets==3.6.0"
34+
"pydantic>=2.7",
35+
"PyYAML>=6.0",
36+
"pillow>=10.0",
37+
"numpy>=1.24",
38+
"arabic-reshaper>=3.0",
39+
"python-bidi>=0.4.2",
40+
"typer>=0.12",
41+
"rich>=13.0",
42+
"tqdm>=4.66",
2343
]
2444

2545
[project.optional-dependencies]
46+
# Tabular / HuggingFace text sources and dataset export targets.
47+
data = [
48+
"pandas>=2.0",
49+
"pyarrow>=14.0",
50+
"datasets>=2.19",
51+
"huggingface-hub>=0.23",
52+
]
2653
dev = [
27-
"pytest==8.4.1",
28-
"pytest-cov==6.2.1"
54+
"pytest>=8.0",
55+
"pytest-cov>=5.0",
56+
"ruff>=0.5",
57+
]
58+
all = [
59+
"ocrsmith[data,dev]",
2960
]
3061

62+
[project.urls]
63+
Homepage = "https://github.com/atlasia-ma/OCRSmith"
64+
Repository = "https://github.com/atlasia-ma/OCRSmith"
65+
Issues = "https://github.com/atlasia-ma/OCRSmith/issues"
66+
3167
[project.scripts]
3268
ocrsmith = "ocrsmith.cli:main"
3369

3470
[tool.setuptools.packages.find]
3571
where = ["src"]
72+
73+
[tool.setuptools.package-data]
74+
ocrsmith = ["config/*.yaml", "config/presets/*.yaml"]
75+
76+
[tool.pytest.ini_options]
77+
minversion = "8.0"
78+
# Makes `pytest` work straight from a clone, without an editable install.
79+
pythonpath = ["src"]
80+
testpaths = ["tests"]
81+
addopts = "-ra --strict-markers"
82+
markers = [
83+
"slow: tests that render many samples or touch the network",
84+
"network: tests that require internet access",
85+
]
86+
filterwarnings = ["ignore::DeprecationWarning"]
87+
88+
[tool.coverage.run]
89+
branch = true
90+
source = ["src/ocrsmith"]
91+
92+
[tool.coverage.report]
93+
exclude_lines = [
94+
"pragma: no cover",
95+
"raise NotImplementedError",
96+
"if TYPE_CHECKING:",
97+
]
98+
99+
[tool.ruff]
100+
line-length = 110
101+
target-version = "py310"
102+
src = ["src", "tests"]
103+
104+
[tool.ruff.lint]
105+
select = ["E", "F", "I", "UP", "B", "C4", "SIM"]
106+
ignore = [
107+
"E501", # line length is handled by the formatter
108+
"B008", # typer uses callables in argument defaults by design
109+
"SIM108", # ternaries are not always clearer
110+
]
111+
112+
[tool.ruff.lint.per-file-ignores]
113+
# Re-exporting is what an __init__.py is for; the packages that care declare __all__.
114+
"__init__.py" = ["F401"]
115+
"tests/*" = ["B011"]

src/ocrsmith/cli.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33

44
def main() -> None:
5-
app_main()
5+
app_main()
66

77

88
if __name__ == "__main__":
9-
main()
9+
main()

src/ocrsmith/config/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# src/ocrsmith/config/__init__.py
2-
from .schema import AppConfig
32
from .loader import load_config
3+
from .schema import AppConfig

src/ocrsmith/config/loader.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
# src/ocrsmith/config/loader.py
2-
from .schema import AppConfig
3-
import yaml
42
import os
53

4+
import yaml
5+
6+
from .schema import AppConfig
7+
8+
69
def load_config(config_path=None):
710
if config_path is None:
811
config_path = os.path.join(os.path.dirname(__file__), "default_config.yaml")
9-
with open(config_path, "r") as f:
12+
with open(config_path) as f:
1013
config_dict = yaml.safe_load(f)
1114

1215
config = AppConfig(**config_dict)
13-
return config
16+
return config

0 commit comments

Comments
 (0)