Skip to content

Commit c49b94f

Browse files
committed
refactoring upd
1 parent 1d7b4a2 commit c49b94f

21 files changed

Lines changed: 1797 additions & 635 deletions

.dockerignore

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
.git
1+
.git
22
.gitignore
33
.venv
44
__pycache__
55
*.pyc
66
*.pyo
77
*.pyd
8+
.pytest_cache
9+
.ruff_cache
10+
.mypy_cache
811
.env
912
data/raw
1013
data/clean
11-
notebooks
14+
notebooks

.env.example

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1-
DB_HOST=db
1+
DB_HOST=localhost
22
DB_PORT=5432
3-
DB_USER=news_user
3+
DB_USER=postgres
44
DB_PASSWORD=CHANGE_ME_STRONG_PASSWORD
55
DB_ADMIN_DB=postgres
66
DB_NEWS=news_db
7-
NEWSAPI_KEY=YOUR_REAL_NEWSAPI_KEY
7+
8+
NEWSAPI_KEY=YOUR_REAL_NEWSAPI_KEY
9+
NEWSAPI_URL=https://newsapi.org/v2/everything
10+
NEWSAPI_DEFAULT_LANGUAGE=ru
11+
NEWSAPI_SORT_BY=publishedAt
12+
13+
REQUEST_TIMEOUT_SECONDS=15
14+
REQUEST_MAX_RETRIES=3
15+
REQUEST_BACKOFF_FACTOR=1
16+
REQUEST_MAX_BACKOFF_SECONDS=30
17+
REQUEST_PAGE_SIZE_MAX=100
18+
MAX_PAGES_PER_REQUEST=50
19+
NEWSAPI_MAX_TOTAL_RESULTS=1000
20+
21+
DB_CONNECT_TIMEOUT_SECONDS=10
22+
DB_STATEMENT_TIMEOUT_MS=45000

.github/workflows/ci.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: ci
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
quality:
9+
runs-on: ubuntu-latest
10+
11+
steps:
12+
- name: Checkout
13+
uses: actions/checkout@v4
14+
15+
- name: Setup Python
16+
uses: actions/setup-python@v5
17+
with:
18+
python-version: "3.12"
19+
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install -r requirements-dev.txt
24+
25+
- name: Ruff
26+
run: ruff check .
27+
28+
- name: Compile
29+
run: python -m compileall .
30+
31+
- name: Tests
32+
run: pytest
33+
34+
- name: Bandit
35+
run: bandit -r src config main.py
36+
37+
- name: Dependency audit
38+
run: pip-audit

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
.env
1+
.env
22
.vscode/
33
.venv/
44
__pycache__/
55
*.pyc
6+
.pytest_cache/
7+
.ruff_cache/
8+
.mypy_cache/
9+
10+
# Generated ETL artifacts
611
data/raw/*
712
!data/raw/.gitkeep
813
data/clean/*

DockerFile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.12-slim
1+
FROM python:3.12-slim
22

33
WORKDIR /app
44

@@ -10,4 +10,10 @@ RUN pip install --no-cache-dir -r requirements.txt
1010

1111
COPY . .
1212

13-
CMD ["python", "main.py"]
13+
RUN useradd --create-home --shell /bin/bash etluser \
14+
&& mkdir -p /app/data/raw /app/data/clean \
15+
&& chown -R etluser:etluser /app
16+
17+
USER etluser
18+
19+
CMD ["python", "main.py", "--worker", "--bootstrap"]

README.md

Lines changed: 84 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,113 @@
1-
# NEWS_API_ETL_Project
1+
# NEWS_API_ETL_Project
22

3-
### Цель
4-
Обрабатывать из внешнего API новости и используя принцип ETL сохранять нужные новости в базу данных.
3+
Production-oriented ETL pipeline that collects articles from NewsAPI, validates/transforms them, and loads clean records into PostgreSQL.
54

6-
### Стек
7-
Extract: requests(страна, категория, ключевое слово, размер страницы (опционально)) -> сохранение статей в папку data/raw
5+
## What this project does
6+
1. `extract`: gets paginated articles from NewsAPI with timeout, retries, and backoff.
7+
2. `transform`: validates each article, normalizes fields and timestamps, and collects rejection stats.
8+
3. `load`: upserts articles and links them to user requests with deduplication.
9+
4. `worker`: atomically claims queued search requests from PostgreSQL and processes them safely.
810

9-
Transform: Python скрипт читает данные из нового файла data/raw, проверяет чтобы были указаны: автор, заголовок, описание не меньше 20 символов, url ссылка
10-
11-
Load: psycopg2 подключается к PostgreSQL, перед вставкой разрешается конфликт с уникальной url ссылкой. Если такая ссылка уже есть то новость не добавляется в таблицу
12-
13-
db: создается база данных и таблица если их еще нет. url присваивается UNIQUE
14-
15-
Pipeline: программа запускается скриптом разделенным на 5 основных модулей (db.py, extract.py, load.py, transform.py, main.py)
16-
17-
### Схема ETL
18-
Everything из NewsApi (extract) -> Филтр на наличие автора, заголовка, описания не меньше 20 символов, наличие url (transform) -> загрузка статей чей url отсутствует в базе данных (load)
19-
20-
### Структура папок
11+
## Repository structure
2112
```text
2213
project/
23-
├── config
24-
| └── config.py
25-
├── data /
26-
| ├──raw / # тут будут храниться статьи до обработки в формате json
27-
| └──clean / # тут будут храниться статьи после обработки
28-
├── notebooks /
29-
| └── 01_eda.ipynb
30-
├── src /
31-
| ├── __init__.py
32-
| ├── db.py
33-
| ├── extract.py
34-
| ├── load.py
35-
| └── transform.py
36-
├──.env.example
14+
├── config/
15+
│ └── config.py
16+
├── data/
17+
│ ├── raw/
18+
│ └── clean/
19+
├── notebooks/
20+
│ └── 01_eda.ipynb
21+
├── src/
22+
│ ├── __init__.py
23+
│ ├── db.py
24+
│ ├── extract.py
25+
│ ├── load.py
26+
│ ├── pipeline.py
27+
│ ├── transform.py
28+
│ └── worker.py
29+
├── .env.example
30+
├── DockerFile
31+
├── docker-compose.yml
3732
├── main.py
38-
└── requirements.txt
33+
├── requirements.txt
34+
└── requirements-dev.txt
3935
```
4036

41-
### Порядок запуска
42-
#### склонировать репозиторий
37+
## Environment variables
38+
Copy `.env.example` to `.env` and fill your values:
39+
4340
```bash
44-
git clone [сслыка на репозиторий]
41+
cp .env.example .env
4542
```
4643

47-
#### перейти в папку проекта
44+
Key variables:
45+
- `NEWSAPI_KEY`: required for extract.
46+
- `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NEWS`.
47+
- Optional reliability settings: `REQUEST_MAX_RETRIES`, `REQUEST_TIMEOUT_SECONDS`, `MAX_PAGES_PER_REQUEST`, `DB_CONNECT_TIMEOUT_SECONDS`.
48+
49+
## Local run
50+
### 1. Install
4851
```bash
49-
cd <repo_name>
52+
python -m venv .venv
53+
.venv\Scripts\activate
54+
pip install -r requirements.txt
5055
```
51-
#### создать виртуальное окружение
56+
57+
### 2. Initialize DB objects
5258
```bash
53-
py -m venv .venv
59+
python main.py --init-only
5460
```
55-
после чего
61+
Alternative for one-shot run: add `--bootstrap` to any run command below.
62+
63+
### 3. Debug run (raw/clean JSON + DB table `bad_news_bears`)
5664
```bash
57-
.venv\Scripts\Activate.ps1
65+
python main.py --debug --keyword python --limit 20 --page_size 50 --language en
5866
```
5967

60-
#### установить библиотеки
61-
``` bash
62-
pip install -r requirements.txt
68+
### 4. Web-mode run (requires existing `app_users` and `search_requests` rows)
69+
```bash
70+
python main.py --keyword python --limit 20 --page_size 50 --language en --user_id 1 --search_request_id 1
6371
```
6472

65-
#### скопировать .env.exsample
66-
``` bash
67-
copy .env.example .env
73+
### 5. Worker loop
74+
```bash
75+
python main.py --worker --poll_interval 3
6876
```
69-
Заполнить .env вашими данными
7077

71-
#### запустить код
78+
## Docker
79+
Run app + Postgres with Docker Compose:
80+
7281
```bash
73-
python main.py --keyword your_key_word --limit your_articles_limit --page_size your_page_size
82+
docker compose up --build
7483
```
7584

76-
### пример .env
77-
```.env
78-
DB_HOST=localhost
79-
DB_PORT=5432
80-
DB_USER=postgres
81-
DB_PASSWORD=1234
82-
DB_ADMIN_DB=postgres
83-
DB_NEWS=db_news
84-
NEWSAPI_KEY =your_key
85+
By default, app container runs worker mode.
86+
87+
## Quality checks
88+
```bash
89+
python -m compileall .
90+
ruff check .
91+
pytest -q
92+
bandit -r src config main.py
93+
pip-audit
8594
```
8695

87-
### Важные моменты
88-
В папку data/raw сохраняются все статьи которые были получены за 1 запрос по вашим критериям. Им в качестве имени присваивается текущее дата-время, ключевое слово и текущая страница.
96+
## Reliability and safety guarantees
97+
- Request retry with backoff and HTTP status handling (`429`, `5xx`).
98+
- Input validation in transform layer (bad records are rejected with reason stats).
99+
- Idempotent article upsert by URL.
100+
- Atomic worker dequeue (`FOR UPDATE SKIP LOCKED`) to avoid duplicate processing across workers.
101+
- DB connection timeout and statement timeout support.
102+
103+
## Notes
104+
- `.env`, `.venv`, `__pycache__`, and generated files under `data/raw` and `data/clean` are ignored by git.
105+
- Keep secrets only in `.env` (never commit real keys).
89106

90-
В папку data/clean попадают все статьи которые имеют: автора, заголовок, описание 20+ символов и url ссылку. им присвается имя аналогичным способом что и в data/raw однако первым словом в имени является cleaned
107+
## Troubleshooting
108+
- `Database 'news_db' does not exist`:
109+
- Run `python main.py --init-only` once, or add `--bootstrap` to your run command.
110+
- `password authentication failed`:
111+
- Verify `.env` values `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`.
112+
- Manually test credentials with psql/pgAdmin for the same host/port/user.
91113

92-
В папке data/clean/stats содержит статистику по отклоненным статьям. Считаются все недочеты статей, а так же высчитывается первая блокирующая ошибка каждой статьи.

0 commit comments

Comments
 (0)