npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

no-timeout-csv

v0.1.6

Published

Parameterized SQL reports with background export to CSV, for reports that can take hours or days to build. Server in pure Python (no third-party dependencies except the DB connector), client in pure JS with no frameworks.

Downloads

1,013

Readme

NoTimeoutCSV

Простые CSV-отчёты — параметризованные SQL-отчёты с выгрузкой в CSV в фоне, без ограничений по объёму и без риска словить таймаут веб-сервера. Каждый запуск — отдельный процесс: генерация может идти часами или сутками, не блокируя сервер. История запусков не подчищается автоматически — всегда можно сравнить с тем, что было вчера.

Живое демо (со статичными примерами, без реальной БД): https://windowrepino.ru/notimeoutcsv/index.html Видео: https://youtu.be/6V1Tl244W3Q

Скриншоты

| Список отчётов | Параметры запуска | |---|---| | Список отчётов | Параметры запуска |

| История выгрузок | Конструктор отчёта | |---|---| | История выгрузок | Конструктор отчёта |

Быстрый старт

Для корректной работы приложения нужно создать и заполнить файл .env. Можно просто переименовать .env.example и прописать свои значения.

cd server
pip install -r requirements.txt
cp .env.example .env   # впишите свои настройки БД
python server.py

Откройте http://127.0.0.1:8000 — клиент отдаётся тем же процессом, отдельно поднимать ничего не нужно.

Через npm

npm install no-timeout-csv
python node_modules/no-timeout-csv/server/server.py

(Node здесь используется только как способ доставки файлов на диск — сам сервер на Python, node для его работы не нужен.)

При установке через npm стоит сразу задать CONFIGS_DIR/RESULTS_DIR в .env на путь снаружи node_modules.

Как держать конфиги вне node_modules

npm install кладёт и server/.env, и client/js/config.js внутрь node_modules, а она слетает при каждой переустановке. Держите конфиги отдельно — через два необязательных флага:

python node_modules/no-timeout-csv/server/server.py \
  --server-config /путь/до/своего/server.env \
  --client-config /путь/до/своего/config.js

Оба флага необязательные и независимые друг от друга — можно передать один, оба или ни одного (тогда используются встроенные файлы).

Важно: переменная окружения ОС всегда побеждает значение из .env (так же по умолчанию ведёт себя, например, python-dotenv). Если настройка как будто игнорируется, хотя --server-config указан верно — проверьте, не осталась ли CONFIGS_DIR/RESULTS_DIR (или любой другой ключ) уже выставленной в текущей сессии терминала с более раннего теста — забытый set CONFIGS_DIR=... будет молча перебивать файл каждый раз.

Настройки — два отдельных файла

Сервер и клиент — не один процесс, а два независимых куска, которые не читают настройки друг друга. Поэтому и файлов с настройками два:

  • server/.env — всё, что нужно серверу: подключение к БД, MAX_WORKERS, PORT, HOST, CONFIGS_DIR/RESULTS_DIR, CSV_DELIMITER, HELPER_TIMEOUT_SECONDS, язык серверных сообщений. Полный и всегда актуальный список — в server/.env.example.
  • client/js/config.js — то, что нужно браузеру: язык интерфейса, интервалы поллинга истории. Это обычный статический JS-файл, отдаётся браузеру как есть, безо всякой обработки сервером.

Оба правятся вручную, простым текстовым редактором, без пересборки — так же, как и всё остальное в проекте.

Структура

  • server/ — Python, стандартная библиотека (кроме коннектора к БД)
    • server.py — HTTP API + оркестратор воркеров, с БД не работает
    • worker.py — отдельный процесс на каждый запуск отчёта, пишет CSV
    • param_options.py — короткоживущий процесс: варианты для списка/мультивыбора, подсказки мин/макс для дат
    • report_columns.py — короткоживущий процесс: список колонок для конструктора отчёта
    • connectors/ — плагины подключения к БД (образец: postgresql.py)
    • configs/ — конфиги отчётов (SQL-запрос, параметры, маппинг колонок)
    • results/ — сюда копятся готовые файлы, по одному на запуск
  • client/ — чистый JS, без сборки и фреймворков
    • index.html — список отчётов
    • report.html — запуск отчёта и история его выгрузок
    • settings.html — создание/редактирование отчёта (SQL, колонки, параметры)

Язык интерфейса

По умолчанию — русский. Переключается в двух местах отдельно (клиент и сервер не читают настройки друг друга):

  • КлиентLANGUAGE: "ru" / "en" в client/js/config.js.
  • СерверLANGUAGE=ru / en в server/.env.

Как добавить/настроить отчёт

В server/configs/ уже есть готовый пример — sales_data. Он показывает на практике всё сразу: обычные параметры (region), даты с подсказками мин/макс (date_from/date_to), мультивыбор (products), и column_mapping для заголовков CSV. Удобно открыть его в "Настройках" просто чтобы посмотреть, как всё это собирается вместе, прежде чем писать свой. Учтите: сам пример ссылается на таблицу sales_data, которой в вашей базе, скорее всего, нет — это образец структуры конфига, не готовый к запуску демо-набор данных.

Проще всего — через интерфейс: "+ Новый отчёт" на главной странице, или "Настройки" на странице уже существующего отчёта. Форма позволяет задать название, SQL-запрос, колонки и параметры, ничего руками в файлах редактировать не нужно.

При желании конфиг можно и написать/поправить руками — это просто .json в server/configs/, никакой магии:

{
  "id": "my_report",
  "name": "Название",
  "connector": "postgresql",
  "sql": "SELECT ... WHERE col = %(param)s",
  "columns_query": "SELECT * FROM my_table LIMIT 0",
  "column_mapping": {"col": "Колонка"},
  "params": [{"name": "param", "view_name": "Параметр", "type": "string"}]
}

Удаление отчёта

Кнопка "Удалить" рядом с каждым отчётом в общем списке (index.html) — сначала спрашивает подтверждение (на случай случайного клика), а после него удаляет всё сразу и необратимо: и сам конфиг (configs/<id>.json), и всю папку с накопленной историей выгрузок (results/<id>/). Частичного удаления (например, только конфиг, но не файлы) нет.

Как параметры на самом деле попадают в запрос

%(имя)s — это плейсхолдер psycopg2 (именованный, безопасный от SQL-инъекций — значение передаётся драйверу отдельно от текста запроса, а не подставляется как строка). Имя внутри скобок должно точно совпадать с полем "Имя переменной" параметра в настройках — с учётом регистра.

Важно: параметр из списка "Параметры" — это только описание для формы (какое поле показать пользователю, какого типа, откуда брать варианты для списка). Сам он ни на что не влияет, пока вы не написали %(имя)s где-то в тексте SQL-запроса. Добавили параметр region в настройках, но в SQL нигде нет %(region)s — пользователь увидит поле "Регион" на экране, что-то там выберет, но запрос выполнится так, будто этого поля не существует: psycopg2 просто не находит в тексте запроса место для этого значения и молча его игнорирует, никакой ошибки не будет. Иными словами: список параметров описывает форму, а %(имя)s в SQL — это то, что реально что-то делает.

Параметр не заполнен ("Все")

Если пользователь оставил параметр как "Все" (не ввёл значение/ничего не выбрал), сервер передаёт в запрос настоящий SQL NULL. Стандартный способ сделать фильтр необязательным — писать условие в паре с проверкой на NULL, оборачивая весь блок в скобки:

WHERE (%(region)s IS NULL OR region = %(region)s)
  AND (%(revenue)s IS NULL OR revenue >= %(revenue)s)

Скобки вокруг каждого блока обязательны — AND в SQL связывает крепче, чем OR, и без скобок ... AND %(x)s IS NULL OR revenue >= %(x)s разберётся не так, как ожидается (второе условие "вывалится" из-под остальных фильтров).

Для мультивыбора (type: "multilist") параметр приходит списком — используйте = ANY(%(имя)s::text[]) (тип массива подберите под свою колонку, для не-текстовых — например ::int[]).

Типы параметров

  • string, number, date — обычное поле ввода.
  • date может дополнительно иметь min_query/max_query — SQL (одна колонка, одно значение), результат которого показывается как подсказка рядом с названием поля (например "Дата с (мин. 20.05.2022)"). Это только подсказка, не ограничение — выбрать более раннюю дату всё равно можно.
  • multilist — мультивыбор с поиском, требует list_query — SQL, отдающий варианты (1-я колонка — значение, 2-я опционально — подпись).

Если операция в SQL требует явно знать тип NULL (например, %(date)s + interval '1 day' — сложение с интервалом неоднозначно для нетипизированного NULL) — добавьте явный каст: %(date)s::timestamp. Для простых сравнений (=, >=, <=) это обычно не нужно — тип и так однозначно выводится из типа колонки.

Колонки

columns_query — отдельный SQL (например, тот же основной запрос с LIMIT 0, или вызов хранимой процедуры) — выполняется в настройках по кнопке "Загрузить колонки", возвращает список имён без данных. Дальше каждой колонке назначается своё название для заголовка CSV — это и есть column_mapping. Оба поля опциональны: без них CSV просто использует сырые имена колонок из БД как есть.

Свой коннектор к другой БД

Можно создать свой коннектор к любой БД, а не только к PostgreSQL. Достаточно написать свой файл в server/connectors/, с NAME и функцией stream_query(query, params) -> (columns, rows) — см. postgresql.py как образец.

Лицензия

Бесплатно для личного, образовательного и некоммерческого использования — см. LICENSE.

Для коммерческого использования нужна отдельная лицензия — см. LICENSE.commercial или напишите на [email protected].