Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v4

- name: Install dependencies
run: uv sync --group dev

- name: Lint
run: uv run ruff check chatgpt_export_tool tests

- name: Format check
run: uv run ruff format --check chatgpt_export_tool tests

- name: Test
run: uv run pytest --cov=chatgpt_export_tool --cov-report=term-missing
28 changes: 28 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Publish to PyPI

on:
release:
types: [published]

permissions:
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
environment: pypi
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Install build tools
run: pip install build

- name: Build package
run: python -m build

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
11 changes: 8 additions & 3 deletions chatgpt_export_tool/commands/analyze.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Analyze command for chatgpt_export_tool."""

import argparse
from datetime import datetime
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

Expand All @@ -11,6 +11,7 @@
AnalyzeConfig,
format_analysis_text,
)
from chatgpt_export_tool.core.config.models import DEFAULT_TIME_FORMAT
from chatgpt_export_tool.core.file_utils import get_file_size
from chatgpt_export_tool.core.parser import JSONParser
from chatgpt_export_tool.core.utils import format_size
Expand Down Expand Up @@ -54,11 +55,15 @@ def _execute(self) -> None:
results = parser.analyze(verbose=self.logger.level <= 20)
results["file_size"] = format_size(file_size)
results["filepath"] = self.filepath
results["analysis_date"] = datetime.now().strftime("%H:%M %d-%m-%Y")
time_format = DEFAULT_TIME_FORMAT
results["analysis_date"] = datetime.now(tz=timezone.utc).strftime(time_format)

output = format_analysis_text(
results,
AnalyzeConfig(include_fields=self.include_fields),
AnalyzeConfig(
include_fields=self.include_fields,
time_format=time_format,
),
)

if self.output_file:
Expand Down
8 changes: 6 additions & 2 deletions chatgpt_export_tool/core/analysis_formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ class AnalyzeConfig:

Attributes:
include_fields: Whether to include field coverage details.
time_format: strftime format for date/time display.
"""

include_fields: bool = False
time_format: str = "%H:%M %d-%m-%Y"


def format_analysis_text(
Expand Down Expand Up @@ -56,8 +58,10 @@ def format_analysis_text(
lines.append(f"Total message nodes in mappings: {results['message_count']:,}")

if results.get("min_date") is not None and results.get("max_date") is not None:
lines.append(f"From: {format_timestamp(results['min_date'])}")
lines.append(f"To: {format_timestamp(results['max_date'])}")
lines.append(
f"From: {format_timestamp(results['min_date'], config.time_format)}"
)
lines.append(f"To: {format_timestamp(results['max_date'], config.time_format)}")

lines.append("")

Expand Down
4 changes: 3 additions & 1 deletion chatgpt_export_tool/core/output/paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,6 @@ def get_unique_filepath(
candidate = filepath.with_name(f"{filepath.stem}_{suffix}{filepath.suffix}")
if candidate not in used_paths and not candidate.exists():
return candidate
raise RuntimeError(f"Could not find unique path for {filepath} after 10000 attempts")
raise RuntimeError(
f"Could not find unique path for {filepath} after 10000 attempts"
)
6 changes: 4 additions & 2 deletions chatgpt_export_tool/core/transcript/access.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Shared read-only helpers for conversation structures."""

from datetime import datetime
from datetime import datetime, timezone
from typing import Any, Iterator, Optional

from .thread import (
Expand Down Expand Up @@ -86,7 +86,9 @@ def get_date_group_key(conversation: dict[str, Any]) -> Optional[str]:
return None

try:
return datetime.fromtimestamp(float(create_time)).strftime("%Y-%m-%d")
return datetime.fromtimestamp(float(create_time), tz=timezone.utc).strftime(
"%Y-%m-%d"
)
except (TypeError, ValueError, OSError):
return None

Expand Down
10 changes: 5 additions & 5 deletions chatgpt_export_tool/core/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Small shared formatting helpers."""

from datetime import datetime
from datetime import datetime, timezone


def format_size(size_bytes: int) -> str:
Expand All @@ -21,14 +21,14 @@ def format_size(size_bytes: int) -> str:


def format_timestamp(timestamp: float, time_format: str = "%H:%M %d-%m-%Y") -> str:
"""Format a Unix timestamp to human-readable date string.
"""Format a Unix timestamp to a UTC date string.

Args:
timestamp: Unix timestamp (seconds since epoch). May be float, int, or Decimal.
time_format: strftime format string.

Returns:
Formatted date string.
Formatted UTC date string.
"""
# Handle Decimal values from JSON parser
dt = datetime.fromtimestamp(float(timestamp))
dt = datetime.fromtimestamp(float(timestamp), tz=timezone.utc)
return dt.strftime(time_format)
Loading