pdf-file-renamer 0.6.0__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/PKG-INFO +1 -1
  2. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/coverage.xml +76 -49
  3. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/pyproject.toml +1 -1
  4. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/__init__.py +1 -1
  5. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/application/pdf_rename_workflow.py +8 -2
  6. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/doi/pdf2doi_extractor.py +46 -12
  7. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/presentation/formatters.py +15 -3
  8. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/.env.example +0 -0
  9. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/.github/workflows/ci.yml +0 -0
  10. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/.github/workflows/release.yml +0 -0
  11. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/.gitignore +0 -0
  12. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/.python-version +0 -0
  13. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/LICENSE +0 -0
  14. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/README.md +0 -0
  15. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/REFACTORING_SUMMARY.md +0 -0
  16. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/application/__init__.py +0 -0
  17. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/application/filename_service.py +0 -0
  18. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/application/rename_service.py +0 -0
  19. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/domain/__init__.py +0 -0
  20. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/domain/models.py +0 -0
  21. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/domain/ports.py +0 -0
  22. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/__init__.py +0 -0
  23. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/config.py +0 -0
  24. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/doi/__init__.py +0 -0
  25. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/llm/__init__.py +0 -0
  26. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/llm/pydantic_ai_provider.py +0 -0
  27. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/pdf/__init__.py +0 -0
  28. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/pdf/composite.py +0 -0
  29. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/pdf/docling_extractor.py +0 -0
  30. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/infrastructure/pdf/pymupdf_extractor.py +0 -0
  31. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/main.py +0 -0
  32. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/presentation/__init__.py +0 -0
  33. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/src/pdf_file_renamer/presentation/cli.py +0 -0
  34. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/__init__.py +0 -0
  35. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/data/2025-dennis-managing-complexity.pdf +0 -0
  36. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/data/Camp_of_the_Saints.pdf +0 -0
  37. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/data/s43588-025-00854-1.pdf +0 -0
  38. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/test_domain_models.py +0 -0
  39. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/test_filename_service.py +0 -0
  40. {pdf_file_renamer-0.6.0 → pdf_file_renamer-0.6.1}/tests/test_rename_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdf-file-renamer
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: Intelligent PDF renaming using LLMs
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.11
@@ -1,5 +1,5 @@
1
1
  <?xml version="1.0" ?>
2
- <coverage version="7.10.7" timestamp="1760461998034" lines-valid="702" lines-covered="142" line-rate="0.2023" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
2
+ <coverage version="7.10.7" timestamp="1760480007723" lines-valid="729" lines-covered="142" line-rate="0.1948" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0">
3
3
  <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.10.7 -->
4
4
  <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd -->
5
5
  <sources>
@@ -22,7 +22,7 @@
22
22
  </class>
23
23
  </classes>
24
24
  </package>
25
- <package name="src.pdf_file_renamer.application" line-rate="0.5194" branch-rate="0" complexity="0">
25
+ <package name="src.pdf_file_renamer.application" line-rate="0.5154" branch-rate="0" complexity="0">
26
26
  <classes>
27
27
  <class name="__init__.py" filename="src/pdf_file_renamer/application/__init__.py" complexity="0" line-rate="1" branch-rate="0">
28
28
  <methods/>
@@ -82,7 +82,7 @@
82
82
  <line number="172" hits="1"/>
83
83
  </lines>
84
84
  </class>
85
- <class name="pdf_rename_workflow.py" filename="src/pdf_file_renamer/application/pdf_rename_workflow.py" complexity="0" line-rate="0.2245" branch-rate="0">
85
+ <class name="pdf_rename_workflow.py" filename="src/pdf_file_renamer/application/pdf_rename_workflow.py" complexity="0" line-rate="0.22" branch-rate="0">
86
86
  <methods/>
87
87
  <lines>
88
88
  <line number="3" hits="1"/>
@@ -122,18 +122,19 @@
122
122
  <line number="101" hits="0"/>
123
123
  <line number="102" hits="0"/>
124
124
  <line number="105" hits="0"/>
125
- <line number="106" hits="0"/>
126
- <line number="115" hits="0"/>
127
- <line number="125" hits="0"/>
128
- <line number="126" hits="0"/>
129
- <line number="127" hits="0"/>
130
- <line number="128" hits="0"/>
131
- <line number="130" hits="1"/>
132
- <line number="145" hits="0"/>
133
- <line number="146" hits="0"/>
134
- <line number="148" hits="1"/>
135
- <line number="168" hits="0"/>
136
- <line number="169" hits="0"/>
125
+ <line number="107" hits="0"/>
126
+ <line number="112" hits="0"/>
127
+ <line number="121" hits="0"/>
128
+ <line number="131" hits="0"/>
129
+ <line number="132" hits="0"/>
130
+ <line number="133" hits="0"/>
131
+ <line number="134" hits="0"/>
132
+ <line number="136" hits="1"/>
133
+ <line number="151" hits="0"/>
134
+ <line number="152" hits="0"/>
135
+ <line number="154" hits="1"/>
136
+ <line number="174" hits="0"/>
137
+ <line number="175" hits="0"/>
137
138
  </lines>
138
139
  </class>
139
140
  <class name="rename_service.py" filename="src/pdf_file_renamer/application/rename_service.py" complexity="0" line-rate="0.9688" branch-rate="0">
@@ -337,42 +338,66 @@
337
338
  <line number="31" hits="0"/>
338
339
  <line number="33" hits="0"/>
339
340
  <line number="34" hits="0"/>
340
- <line number="38" hits="0"/>
341
341
  <line number="39" hits="0"/>
342
- <line number="42" hits="0"/>
342
+ <line number="40" hits="0"/>
343
+ <line number="43" hits="0"/>
344
+ <line number="44" hits="0"/>
343
345
  <line number="45" hits="0"/>
344
- <line number="46" hits="0"/>
345
346
  <line number="47" hits="0"/>
347
+ <line number="48" hits="0"/>
346
348
  <line number="49" hits="0"/>
347
- <line number="50" hits="0"/>
348
- <line number="51" hits="0"/>
349
- <line number="54" hits="0"/>
349
+ <line number="52" hits="0"/>
350
+ <line number="55" hits="0"/>
350
351
  <line number="57" hits="0"/>
351
352
  <line number="58" hits="0"/>
352
353
  <line number="59" hits="0"/>
353
354
  <line number="60" hits="0"/>
354
355
  <line number="61" hits="0"/>
355
- <line number="63" hits="0"/>
356
+ <line number="62" hits="0"/>
357
+ <line number="65" hits="0"/>
358
+ <line number="68" hits="0"/>
359
+ <line number="69" hits="0"/>
360
+ <line number="70" hits="0"/>
361
+ <line number="71" hits="0"/>
362
+ <line number="72" hits="0"/>
356
363
  <line number="73" hits="0"/>
364
+ <line number="74" hits="0"/>
357
365
  <line number="75" hits="0"/>
366
+ <line number="76" hits="0"/>
358
367
  <line number="77" hits="0"/>
368
+ <line number="78" hits="0"/>
369
+ <line number="79" hits="0"/>
370
+ <line number="80" hits="0"/>
371
+ <line number="83" hits="0"/>
372
+ <line number="84" hits="0"/>
373
+ <line number="85" hits="0"/>
374
+ <line number="86" hits="0"/>
375
+ <line number="87" hits="0"/>
359
376
  <line number="88" hits="0"/>
360
377
  <line number="89" hits="0"/>
361
378
  <line number="92" hits="0"/>
362
- <line number="93" hits="0"/>
363
379
  <line number="95" hits="0"/>
364
- <line number="96" hits="0"/>
365
- <line number="98" hits="0"/>
366
- <line number="100" hits="0"/>
367
- <line number="110" hits="0"/>
380
+ <line number="97" hits="0"/>
381
+ <line number="107" hits="0"/>
382
+ <line number="109" hits="0"/>
368
383
  <line number="111" hits="0"/>
369
- <line number="114" hits="0"/>
370
- <line number="115" hits="0"/>
371
- <line number="117" hits="0"/>
372
- <line number="118" hits="0"/>
373
- <line number="120" hits="0"/>
384
+ <line number="122" hits="0"/>
374
385
  <line number="123" hits="0"/>
386
+ <line number="126" hits="0"/>
387
+ <line number="127" hits="0"/>
375
388
  <line number="129" hits="0"/>
389
+ <line number="130" hits="0"/>
390
+ <line number="132" hits="0"/>
391
+ <line number="134" hits="0"/>
392
+ <line number="144" hits="0"/>
393
+ <line number="145" hits="0"/>
394
+ <line number="148" hits="0"/>
395
+ <line number="149" hits="0"/>
396
+ <line number="151" hits="0"/>
397
+ <line number="152" hits="0"/>
398
+ <line number="154" hits="0"/>
399
+ <line number="157" hits="0"/>
400
+ <line number="163" hits="0"/>
376
401
  </lines>
377
402
  </class>
378
403
  </classes>
@@ -810,34 +835,30 @@
810
835
  <line number="146" hits="0"/>
811
836
  <line number="147" hits="0"/>
812
837
  <line number="148" hits="0"/>
813
- <line number="149" hits="0"/>
814
838
  <line number="150" hits="0"/>
815
- <line number="151" hits="0"/>
816
- <line number="153" hits="0"/>
817
- <line number="160" hits="0"/>
818
- <line number="161" hits="0"/>
819
- <line number="164" hits="0"/>
820
- <line number="165" hits="0"/>
839
+ <line number="155" hits="0"/>
840
+ <line number="156" hits="0"/>
841
+ <line number="157" hits="0"/>
842
+ <line number="159" hits="0"/>
843
+ <line number="166" hits="0"/>
844
+ <line number="167" hits="0"/>
845
+ <line number="170" hits="0"/>
821
846
  <line number="171" hits="0"/>
822
- <line number="173" hits="0"/>
823
- <line number="174" hits="0"/>
824
- <line number="175" hits="0"/>
825
- <line number="176" hits="0"/>
847
+ <line number="177" hits="0"/>
848
+ <line number="179" hits="0"/>
826
849
  <line number="180" hits="0"/>
827
850
  <line number="181" hits="0"/>
828
- <line number="183" hits="0"/>
829
- <line number="184" hits="0"/>
830
- <line number="185" hits="0"/>
851
+ <line number="182" hits="0"/>
831
852
  <line number="186" hits="0"/>
832
853
  <line number="187" hits="0"/>
833
854
  <line number="189" hits="0"/>
834
855
  <line number="190" hits="0"/>
856
+ <line number="191" hits="0"/>
857
+ <line number="192" hits="0"/>
835
858
  <line number="193" hits="0"/>
859
+ <line number="195" hits="0"/>
836
860
  <line number="196" hits="0"/>
837
- <line number="197" hits="0"/>
838
861
  <line number="199" hits="0"/>
839
- <line number="200" hits="0"/>
840
- <line number="201" hits="0"/>
841
862
  <line number="202" hits="0"/>
842
863
  <line number="203" hits="0"/>
843
864
  <line number="205" hits="0"/>
@@ -845,7 +866,13 @@
845
866
  <line number="207" hits="0"/>
846
867
  <line number="208" hits="0"/>
847
868
  <line number="209" hits="0"/>
869
+ <line number="211" hits="0"/>
870
+ <line number="212" hits="0"/>
871
+ <line number="213" hits="0"/>
872
+ <line number="214" hits="0"/>
848
873
  <line number="216" hits="0"/>
874
+ <line number="221" hits="0"/>
875
+ <line number="228" hits="0"/>
849
876
  </lines>
850
877
  </class>
851
878
  </classes>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pdf-file-renamer"
3
- version = "0.6.0"
3
+ version = "0.6.1"
4
4
  description = "Intelligent PDF renaming using LLMs"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """PDF Renamer - Intelligent PDF file renaming using LLMs."""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.6.1"
@@ -5,7 +5,7 @@ import contextlib
5
5
  from collections.abc import Callable
6
6
  from pathlib import Path
7
7
 
8
- from pdf_file_renamer.domain.models import FileRenameOperation
8
+ from pdf_file_renamer.domain.models import ConfidenceLevel, FileRenameOperation
9
9
  from pdf_file_renamer.domain.ports import (
10
10
  DOIExtractor,
11
11
  FilenameGenerator,
@@ -103,12 +103,18 @@ class PDFRenameWorkflow:
103
103
 
104
104
  # Mark complete
105
105
  if status_callback:
106
+ # result.confidence is already a string due to use_enum_values=True
107
+ confidence_str = (
108
+ result.confidence.value
109
+ if isinstance(result.confidence, ConfidenceLevel)
110
+ else result.confidence
111
+ )
106
112
  status_callback(
107
113
  filename,
108
114
  {
109
115
  "status": "Complete",
110
116
  "stage": "✓",
111
- "confidence": result.confidence.value,
117
+ "confidence": confidence_str,
112
118
  },
113
119
  )
114
120
 
@@ -31,16 +31,14 @@ class PDF2DOIExtractor(DOIExtractor):
31
31
  try:
32
32
  # Run pdf2doi in executor to avoid blocking
33
33
  loop = asyncio.get_event_loop()
34
- results = await loop.run_in_executor(
34
+ result = await loop.run_in_executor(
35
35
  None, pdf2doi.pdf2doi, str(pdf_path)
36
36
  )
37
37
 
38
- if not results or len(results) == 0:
38
+ # pdf2doi returns a dict (not a list)
39
+ if not result or not isinstance(result, dict):
39
40
  return None
40
41
 
41
- # Get the first result
42
- result = results[0]
43
-
44
42
  # Check if DOI was found
45
43
  identifier = result.get("identifier")
46
44
  if not identifier:
@@ -50,15 +48,51 @@ class PDF2DOIExtractor(DOIExtractor):
50
48
  if identifier_type.lower() not in ("doi", "arxiv"):
51
49
  return None
52
50
 
53
- # Extract metadata from validation_info (bibtex)
51
+ # Extract metadata from validation_info (JSON string from CrossRef API)
54
52
  validation_info = result.get("validation_info", "")
55
53
 
56
- # Parse bibtex for metadata
57
- title = self._extract_bibtex_field(validation_info, "title")
58
- authors = self._extract_bibtex_authors(validation_info)
59
- year = self._extract_bibtex_field(validation_info, "year")
60
- journal = self._extract_bibtex_field(validation_info, "journal")
61
- publisher = self._extract_bibtex_field(validation_info, "publisher")
54
+ # Parse JSON metadata
55
+ import json
56
+
57
+ metadata = {}
58
+ if validation_info:
59
+ try:
60
+ metadata = json.loads(validation_info)
61
+ except json.JSONDecodeError:
62
+ pass
63
+
64
+ # Extract title
65
+ title = metadata.get("title")
66
+
67
+ # Extract authors (list of dicts with 'given' and 'family' fields)
68
+ authors = None
69
+ if "author" in metadata:
70
+ author_list = metadata["author"]
71
+ authors = []
72
+ for author in author_list:
73
+ if isinstance(author, dict):
74
+ family = author.get("family", "")
75
+ given = author.get("given", "")
76
+ if family:
77
+ full_name = f"{given} {family}".strip() if given else family
78
+ authors.append(full_name)
79
+ if not authors:
80
+ authors = None
81
+
82
+ # Extract year from published-online or published
83
+ year = None
84
+ for date_field in ["published-online", "published", "created"]:
85
+ if date_field in metadata and "date-parts" in metadata[date_field]:
86
+ date_parts = metadata[date_field]["date-parts"]
87
+ if date_parts and len(date_parts) > 0 and len(date_parts[0]) > 0:
88
+ year = str(date_parts[0][0])
89
+ break
90
+
91
+ # Extract journal (container-title)
92
+ journal = metadata.get("container-title")
93
+
94
+ # Extract publisher
95
+ publisher = metadata.get("publisher")
62
96
 
63
97
  return DOIMetadata(
64
98
  doi=identifier,
@@ -7,7 +7,7 @@ from rich.prompt import Prompt
7
7
  from rich.table import Table
8
8
  from rich.text import Text
9
9
 
10
- from pdf_file_renamer.domain.models import FileRenameOperation
10
+ from pdf_file_renamer.domain.models import ConfidenceLevel, FileRenameOperation
11
11
 
12
12
 
13
13
  class ProgressDisplay:
@@ -146,7 +146,13 @@ class InteractivePrompt:
146
146
  info_text.append("Suggested: ", style="bold green")
147
147
  info_text.append(f"{operation.new_filename}\n", style="green")
148
148
  info_text.append("Confidence: ", style="bold yellow")
149
- info_text.append(f"{operation.confidence.value}\n", style="yellow")
149
+ # Handle both enum and string confidence
150
+ conf_str = (
151
+ operation.confidence.value
152
+ if isinstance(operation.confidence, ConfidenceLevel)
153
+ else operation.confidence
154
+ )
155
+ info_text.append(f"{conf_str}\n", style="yellow")
150
156
  info_text.append("Reasoning: ", style="bold white")
151
157
  info_text.append(operation.reasoning, style="dim white")
152
158
 
@@ -206,10 +212,16 @@ class ResultsTable:
206
212
  reasoning = op.reasoning
207
213
  if len(reasoning) > 100:
208
214
  reasoning = reasoning[:100] + "..."
215
+ # Handle both enum and string confidence
216
+ conf_str = (
217
+ op.confidence.value
218
+ if isinstance(op.confidence, ConfidenceLevel)
219
+ else op.confidence
220
+ )
209
221
  table.add_row(
210
222
  op.original_path.name,
211
223
  op.new_filename,
212
- op.confidence.value,
224
+ conf_str,
213
225
  reasoning,
214
226
  )
215
227