folio-migration-tools 1.9.10__tar.gz → 1.10.0b1__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 (67) hide show
  1. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/PKG-INFO +18 -28
  2. folio_migration_tools-1.10.0b1/pyproject.toml +146 -0
  3. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/__init__.py +3 -4
  4. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/__main__.py +44 -31
  5. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/circulation_helper.py +114 -105
  6. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/custom_dict.py +2 -2
  7. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/custom_exceptions.py +4 -5
  8. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/folder_structure.py +1 -1
  9. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/helper.py +1 -1
  10. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/library_configuration.py +65 -37
  11. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapper_base.py +38 -25
  12. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/courses_mapper.py +1 -1
  13. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/holdings_mapper.py +7 -3
  14. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/item_mapper.py +13 -26
  15. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/manual_fee_fines_mapper.py +1 -2
  16. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/mapping_file_mapper_base.py +13 -11
  17. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/order_mapper.py +6 -5
  18. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/organization_mapper.py +3 -3
  19. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/user_mapper.py +43 -28
  20. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/conditions.py +84 -70
  21. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/holdings_statementsparser.py +13 -5
  22. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/hrid_handler.py +3 -2
  23. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/marc_file_processor.py +14 -22
  24. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_authorities.py +1 -0
  25. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_base.py +46 -36
  26. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_bibs.py +25 -15
  27. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/rules_mapper_holdings.py +62 -32
  28. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_report.py +1 -1
  29. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/authority_transformer.py +1 -2
  30. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/batch_poster.py +78 -68
  31. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/bibs_transformer.py +12 -7
  32. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/courses_migrator.py +2 -3
  33. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/holdings_csv_transformer.py +14 -15
  34. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/holdings_marc_transformer.py +11 -21
  35. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/items_transformer.py +17 -30
  36. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/loans_migrator.py +53 -131
  37. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/migration_task_base.py +33 -55
  38. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/orders_transformer.py +21 -39
  39. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/organization_transformer.py +9 -18
  40. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/requests_migrator.py +11 -15
  41. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/reserves_migrator.py +1 -1
  42. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/user_transformer.py +10 -15
  43. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/task_configuration.py +6 -7
  44. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/transaction_migration/legacy_loan.py +15 -27
  45. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/transaction_migration/legacy_request.py +1 -1
  46. folio_migration_tools-1.9.10/LICENSE +0 -21
  47. folio_migration_tools-1.9.10/pyproject.toml +0 -94
  48. folio_migration_tools-1.9.10/src/folio_migration_tools/test_infrastructure/mocked_classes.py +0 -406
  49. folio_migration_tools-1.9.10/src/folio_migration_tools/transaction_migration/__init__.py +0 -0
  50. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/README.md +0 -0
  51. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/colors.py +0 -0
  52. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/config_file_load.py +0 -0
  53. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/extradata_writer.py +0 -0
  54. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/holdings_helper.py +0 -0
  55. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/i18n_config.py +0 -0
  56. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/__init__.py +0 -0
  57. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/notes_mapper.py +0 -0
  58. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/mapping_file_transformation/ref_data_mapping.py +0 -0
  59. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/__init__.py +0 -0
  60. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/loc_language_codes.xml +0 -0
  61. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/marc_rules_transformation/marc_reader_wrapper.py +0 -0
  62. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/__init__.py +0 -0
  63. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/migration_tasks/manual_fee_fines_transformer.py +0 -0
  64. {folio_migration_tools-1.9.10/src/folio_migration_tools/test_infrastructure → folio_migration_tools-1.10.0b1/src/folio_migration_tools/transaction_migration}/__init__.py +0 -0
  65. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/transaction_migration/legacy_reserve.py +0 -0
  66. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/transaction_migration/transaction_result.py +0 -0
  67. {folio_migration_tools-1.9.10 → folio_migration_tools-1.10.0b1}/src/folio_migration_tools/translations/en.json +0 -0
@@ -1,33 +1,24 @@
1
1
  Metadata-Version: 2.4
2
- Name: folio_migration_tools
3
- Version: 1.9.10
2
+ Name: folio-migration-tools
3
+ Version: 1.10.0b1
4
4
  Summary: A tool allowing you to migrate data from legacy ILS:s (Library systems) into FOLIO LSP
5
- License-Expression: MIT
6
- License-File: LICENSE
7
5
  Keywords: FOLIO,ILS,LSP,Library Systems,MARC21,Library data
8
- Author: Theodor Tolstoy
9
- Author-email: github.teddes@tolstoy.se
10
- Requires-Python: >=3.10,<4.0
11
- Classifier: Programming Language :: Python :: 3
12
- Classifier: Programming Language :: Python :: 3.10
13
- Classifier: Programming Language :: Python :: 3.11
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Programming Language :: Python :: 3.14
17
- Provides-Extra: docs
18
- Requires-Dist: argparse-prompt (>=0.0.5,<0.0.6)
19
- Requires-Dist: art (>=6.5,<7.0)
20
- Requires-Dist: deepdiff (>=6.2.3,<7.0.0)
21
- Requires-Dist: defusedxml (>=0.7.1,<0.8.0)
22
- Requires-Dist: folio-data-import (>=0.4.1)
23
- Requires-Dist: folio-uuid (>=1.0.0,<2.0.0)
24
- Requires-Dist: folioclient (>=0.70.1,<0.71.0)
25
- Requires-Dist: pyaml (>=21.10.1,<22.0.0)
26
- Requires-Dist: pydantic (>=1.10.2,<2.0.0)
27
- Requires-Dist: pyhumps (>=3.7.3,<4.0.0)
28
- Requires-Dist: pymarc (>=5.2.3,<6.0.0)
29
- Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
30
- Requires-Dist: python-i18n (>=0.3.9,<0.4.0)
6
+ Author: Theodor Tolstoy, Lisa Sjögren, Brooks Travis, Jeremy Nelson, Clinton Bradford
7
+ Author-email: Theodor Tolstoy <github.teddes@tolstoy.se>, Brooks Travis <brooks.travis@gmail.com>
8
+ License-Expression: MIT
9
+ Requires-Dist: folioclient>=1.0.1
10
+ Requires-Dist: pyhumps>=3.7.3,<4.0.0
11
+ Requires-Dist: defusedxml>=0.7.1,<1.0.0
12
+ Requires-Dist: python-dateutil>=2.8.2,<3.0.0
13
+ Requires-Dist: folio-uuid>=1.0.0,<2.0.0
14
+ Requires-Dist: pymarc>=5.2.3,<6.0.0
15
+ Requires-Dist: pydantic>=2.12.3,<3.0.0
16
+ Requires-Dist: argparse-prompt>=0.0.5,<1.0.0
17
+ Requires-Dist: deepdiff>=6.2.3,<7.0.0
18
+ Requires-Dist: pyaml>=21.10.1,<22.0.0
19
+ Requires-Dist: python-i18n>=0.3.9,<1.0.0
20
+ Requires-Dist: art>=6.5,<7.0
21
+ Requires-Python: >=3.10, <4.0
31
22
  Project-URL: Documentation, https://folio-migration-tools.readthedocs.io/en/latest/
32
23
  Project-URL: Homepage, https://github.com/folio-fse/folio_migration_tools
33
24
  Project-URL: Repository, https://github.com/FOLIO-FSE/folio_migration_tools
@@ -166,4 +157,3 @@ Translate all new strings, which begin with `TRANSLATE`, then commit.
166
157
  # Running the scripts
167
158
  For information on syntax, what files are needed and produced by the toolkit, refer to the documentation and example files in the [template repository](https://github.com/FOLIO-FSE/migration_repo_template). We are building out the docs section in this repository as well:[Documentation](https://folio-migration-tools.readthedocs.io/en/latest/)
168
159
  ¨
169
-
@@ -0,0 +1,146 @@
1
+ [project]
2
+ name = "folio_migration_tools"
3
+ version = "1.10.0b1"
4
+ description = "A tool allowing you to migrate data from legacy ILS:s (Library systems) into FOLIO LSP"
5
+ authors = [
6
+ {name = "Theodor Tolstoy", email = "github.teddes@tolstoy.se"},
7
+ {name = "Lisa Sjögren"},
8
+ {name = "Brooks Travis", email = "brooks.travis@gmail.com"},
9
+ {name = "Jeremy Nelson"},
10
+ {name = "Clinton Bradford"}
11
+ ]
12
+ license = "MIT"
13
+ readme = "README.md"
14
+ keywords = ["FOLIO", "ILS", "LSP", "Library Systems", "MARC21", "Library data"]
15
+ requires-python = ">=3.10,<4.0"
16
+ dependencies = [
17
+ "folioclient>=1.0.1",
18
+ "pyhumps>=3.7.3,<4.0.0",
19
+ "defusedxml>=0.7.1,<1.0.0",
20
+ "python-dateutil>=2.8.2,<3.0.0",
21
+ "folio-uuid>=1.0.0,<2.0.0",
22
+ "pymarc>=5.2.3,<6.0.0",
23
+ "pydantic>=2.12.3,<3.0.0",
24
+ "argparse-prompt>=0.0.5,<1.0.0",
25
+ "deepdiff>=6.2.3,<7.0.0",
26
+ "pyaml>=21.10.1,<22.0.0",
27
+ "python-i18n>=0.3.9,<1.0.0",
28
+ "art>=6.5,<7.0",
29
+ # "folio-data-import>=0.4.1"
30
+ ]
31
+
32
+ [dependency-groups]
33
+ dev = [
34
+ "pytest>=7.1.3,<8.0.0",
35
+ "coverage[toml]>=6.5.0,<7.0.0",
36
+ "pytest-cov>=4.0.0,<5.0.0",
37
+ "black>=22.10.0,<23.0.0",
38
+ "flake8>=7.3.0,<8.0.0",
39
+ "mypy>=0.982,<1.0.0",
40
+ "flake8-black>=0.3.6,<1.0.0",
41
+ "flake8-bugbear>=24.12.12,<25.0.0",
42
+ "flake8-bandit>=4.1.1,<5.0.0",
43
+ "flake8-isort>=6.1.2,<7.0.0",
44
+ "flake8-docstrings>=1.6.0,<2.0.0",
45
+ "darglint>=1.8.1,<2.0.0",
46
+ "types-requests>=2.28.11.17,<3.0.0",
47
+ "types-python-dateutil>=2.8.19.11,<3.0.0",
48
+ "ipykernel>=6.29.5,<7.0.0",
49
+ "pytest-asyncio>=0.23.0,<0.24.0",
50
+ "lxml>=6.0.2",
51
+ "ty>=0.0.1a27",
52
+ "pyrefly>=0.43.1",
53
+ ]
54
+ docs = [
55
+ "m2r>=0.2.1,<0.3.0",
56
+ "myst-parser>=0.18.1,<4.0.0",
57
+ "sphinx>=6.0.0,<7.0.0",
58
+ "sphinx-autodoc-typehints>=1.17.0,<2.0.0",
59
+ "sphinx-rtd-theme>=1.0.0,<2.0.0",
60
+ "toml>=0.10.2,<0.11.0",
61
+ "sphinx-book-theme>=1.1.4,<2.0.0",
62
+ "sphinx-autobuild>=2024.10.3,<2025.0.0"
63
+ ]
64
+ [project.urls]
65
+ Documentation = "https://folio-migration-tools.readthedocs.io/en/latest/"
66
+ Homepage = "https://github.com/folio-fse/folio_migration_tools"
67
+ Repository = "https://github.com/FOLIO-FSE/folio_migration_tools"
68
+
69
+ [project.scripts]
70
+ folio-migration-tools = "folio_migration_tools.__main__:main"
71
+
72
+ [tool.coverage.report]
73
+ show_missing = false
74
+ fail_under = 10
75
+
76
+ [tool.coverage.paths]
77
+ source = ["src", "*/site-packages"]
78
+
79
+ [tool.coverage.run]
80
+ branch = true
81
+ source = ["folio_migration_tools"]
82
+
83
+ [build-system]
84
+ requires = ["uv_build>=0.8.6,<0.9.0"]
85
+ build-backend = "uv_build"
86
+
87
+ # Linting
88
+ [tool.ruff]
89
+ line-length = 99
90
+ exclude = [
91
+ ".bzr",
92
+ ".direnv",
93
+ ".eggs",
94
+ ".git",
95
+ ".git-rewrite",
96
+ ".hg",
97
+ ".ipynb_checkpoints",
98
+ ".mypy_cache",
99
+ ".nox",
100
+ ".pants.d",
101
+ ".pyenv",
102
+ ".pytest_cache",
103
+ ".pytype",
104
+ ".ruff_cache",
105
+ ".svn",
106
+ ".tox",
107
+ ".venv",
108
+ ".vscode",
109
+ "__pypackages__",
110
+ "_build",
111
+ "build",
112
+ "dist",
113
+ "site-packages",
114
+ "venv",
115
+ "**/tests/*.py",
116
+ "integration_test.py"
117
+ ]
118
+
119
+ indent-width = 4
120
+ target-version = "py310"
121
+
122
+ [tool.ruff.lint]
123
+ select = ["B", "B9", "C", "E", "F", "S", "W"]
124
+ ignore = ["E741", "S113"]
125
+
126
+ fixable = ["ALL"]
127
+ unfixable = []
128
+
129
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
130
+
131
+ [tool.ruff.lint.per-file-ignores]
132
+ "__init__.py" = ["E402", "F401"]
133
+ "**/{tests,docs,tools}/*" = ["E402"]
134
+ "**/test_*.py" = ["S101"]
135
+
136
+ [tool.ruff.format]
137
+ quote-style = "double"
138
+ indent-style = "space"
139
+ skip-magic-trailing-comma = false
140
+ line-ending = "auto"
141
+
142
+ docstring-code-format = false
143
+ docstring-code-line-length = "dynamic"
144
+
145
+ [tool.ruff.lint.mccabe]
146
+ max-complexity = 15
@@ -3,9 +3,8 @@ from typing import Protocol
3
3
 
4
4
  __version__ = importlib.metadata.version("folio_migration_tools")
5
5
 
6
+
6
7
  class StrCoercible(Protocol):
7
- def __repr__(self) -> str:
8
- ...
8
+ def __repr__(self) -> str: ...
9
9
 
10
- def __str__(self) -> str:
11
- ...
10
+ def __str__(self) -> str: ...
@@ -33,23 +33,25 @@ def parse_args(args):
33
33
 
34
34
  parser.add_argument(
35
35
  "task_name",
36
- help=("Task name. Use one of: " f"{tasks_string}"),
36
+ help=(f"Task name. Use one of: {tasks_string}"),
37
37
  nargs="?" if "FOLIO_MIGRATION_TOOLS_TASK_NAME" in environ else None,
38
38
  prompt="FOLIO_MIGRATION_TOOLS_TASK_NAME" not in environ,
39
39
  default=environ.get("FOLIO_MIGRATION_TOOLS_TASK_NAME"),
40
40
  )
41
41
  parser.add_argument(
42
- "--folio_password", "--okapi_password",
42
+ "--folio_password",
43
+ "--okapi_password",
43
44
  help="password for the tenant in the configuration file",
44
45
  prompt="FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD" not in environ,
45
- default=environ.get("FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD"),
46
+ default=environ.get(
47
+ "FOLIO_MIGRATION_TOOLS_FOLIO_PASSWORD",
48
+ environ.get("FOLIO_MIGRATION_TOOLS_OKAPI_PASSWORD"),
49
+ ),
46
50
  secure=True,
47
51
  )
48
52
  parser.add_argument(
49
53
  "--base_folder_path",
50
- help=(
51
- "path to the base folder for this library. Built on migration_repo_template"
52
- ),
54
+ help=("path to the base folder for this library. Built on migration_repo_template"),
53
55
  prompt="FOLIO_MIGRATION_TOOLS_BASE_FOLDER_PATH" not in environ,
54
56
  default=environ.get("FOLIO_MIGRATION_TOOLS_BASE_FOLDER_PATH"),
55
57
  )
@@ -62,34 +64,43 @@ def parse_args(args):
62
64
  prompt=False,
63
65
  )
64
66
  parser.add_argument(
65
- "--version", "-V",
67
+ "--version",
68
+ "-V",
66
69
  help="Show the version of the FOLIO Migration Tools",
67
70
  action="store_true",
68
71
  prompt=False,
69
72
  )
70
73
  return parser.parse_args(args)
71
74
 
75
+
72
76
  def prep_library_config(args):
73
- config_file_humped = merge_load(args.configuration_path)
74
- config_file_humped["libraryInformation"]["okapiPassword"] = args.folio_password
75
- config_file_humped["libraryInformation"]["baseFolder"] = args.base_folder_path
76
- config_file = humps.decamelize(config_file_humped)
77
- library_config = LibraryConfiguration(**config_file["library_information"])
78
- if library_config.ecs_tenant_id:
79
- library_config.is_ecs = True
80
- if library_config.ecs_tenant_id and not library_config.ecs_central_iteration_identifier:
81
- print(
82
- "ECS tenant ID is set, but no central iteration identifier is provided. "
83
- "Please provide the central iteration identifier in the configuration file."
84
- )
85
- sys.exit("ECS Central Iteration Identifier Not Found")
86
- return config_file, library_config
77
+ config_file_humped = merge_load(args.configuration_path)
87
78
 
88
- def print_version(args):
89
- if "-V" in args or "--version" in args:
79
+ # Only set folioPassword if neither folioPassword nor okapiPassword exist in config
80
+ # The Pydantic validator will handle backward compatibility for existing okapiPassword
81
+ if (
82
+ "folioPassword" not in config_file_humped["libraryInformation"]
83
+ and "okapiPassword" not in config_file_humped["libraryInformation"]
84
+ ):
85
+ config_file_humped["libraryInformation"]["folioPassword"] = args.folio_password
86
+
87
+ config_file_humped["libraryInformation"]["baseFolder"] = args.base_folder_path
88
+ config_file = humps.decamelize(config_file_humped)
89
+ library_config = LibraryConfiguration(**config_file["library_information"])
90
+ if library_config.ecs_tenant_id:
91
+ library_config.is_ecs = True
92
+ if library_config.ecs_tenant_id and not library_config.ecs_central_iteration_identifier:
90
93
  print(
91
- f"FOLIO Migration Tools: {metadata.version('folio_migration_tools')}"
94
+ "ECS tenant ID is set, but no central iteration identifier is provided. "
95
+ "Please provide the central iteration identifier in the configuration file."
92
96
  )
97
+ sys.exit("ECS Central Iteration Identifier Not Found")
98
+ return config_file, library_config
99
+
100
+
101
+ def print_version(args):
102
+ if "-V" in args or "--version" in args:
103
+ print(f"FOLIO Migration Tools: {metadata.version('folio_migration_tools')}")
93
104
  sys.exit(0)
94
105
  return None
95
106
 
@@ -122,7 +133,7 @@ def main():
122
133
  task_names = [t.get("name", "") for t in config_file["migration_tasks"]]
123
134
  print(
124
135
  f"Referenced task name {args.task_name} not found in the "
125
- f'configuration file. Use one of {", ".join(task_names)}'
136
+ f"configuration file. Use one of {', '.join(task_names)}"
126
137
  "\nHalting..."
127
138
  )
128
139
  sys.exit("Task Name Not Found")
@@ -134,13 +145,15 @@ def main():
134
145
  )
135
146
  except StopIteration:
136
147
  print(
137
- f'Referenced task {migration_task_config["migration_task_type"]} '
148
+ f"Referenced task {migration_task_config['migration_task_type']} "
138
149
  "is not a valid option. Update your task to incorporate "
139
150
  f"one of {json.dumps([tc.__name__ for tc in task_classes], indent=4)}"
140
151
  )
141
152
  sys.exit("Task Type Not Found")
142
153
  try:
143
- logging.getLogger("httpx").setLevel(logging.WARNING) # Exclude info messages from httpx
154
+ logging.getLogger("httpx").setLevel(
155
+ logging.WARNING
156
+ ) # Exclude info messages from httpx
144
157
  with FolioClient(
145
158
  library_config.gateway_url,
146
159
  library_config.tenant_id,
@@ -161,16 +174,15 @@ def main():
161
174
  logging.critical(json_error)
162
175
  print(json_error.doc)
163
176
  print(
164
- f"\n{json_error}"
165
- f"\nError parsing the above JSON mapping or configruation file. Halting."
177
+ f"\n{json_error}\nError parsing the above JSON mapping or configruation file. Halting."
166
178
  )
167
179
  sys.exit("Invalid JSON")
168
180
  except ValidationError as e:
169
- print(e.json())
181
+ print(json.dumps(e.errors(), indent=2))
170
182
  print("Validation errors in configuration file:")
171
183
  print("==========================================")
172
184
 
173
- for validation_message in json.loads(e.json()):
185
+ for validation_message in e.errors():
174
186
  print(
175
187
  f"{validation_message['msg']}\t"
176
188
  f"{', '.join(humps.camelize(str(x)) for x in validation_message['loc'])}"
@@ -199,6 +211,7 @@ def main():
199
211
  sys.exit(ee.__class__.__name__)
200
212
  sys.exit(0)
201
213
 
214
+
202
215
  def inheritors(base_class):
203
216
  subclasses = set()
204
217
  work = [base_class]
@@ -3,12 +3,12 @@ import json
3
3
  import logging
4
4
  import re
5
5
  import time
6
+ from http import HTTPStatus
6
7
  from typing import Set
7
8
 
8
9
  import httpx
9
10
  import i18n
10
- from folioclient import FolioClient
11
- from httpx import HTTPError
11
+ from folioclient import FolioClient, FolioClientError, FolioConnectionError, FolioValidationError
12
12
 
13
13
  from folio_migration_tools.helper import Helper
14
14
  from folio_migration_tools.migration_report import MigrationReport
@@ -138,7 +138,6 @@ class CirculationHelper:
138
138
  if legacy_loan.proxy_patron_barcode:
139
139
  data.update({"proxyUserBarcode": legacy_loan.proxy_patron_barcode})
140
140
  path = "/circulation/check-out-by-barcode"
141
- url = f"{self.folio_client.gateway_url}{path}"
142
141
  try:
143
142
  if legacy_loan.patron_barcode in self.missing_patron_barcodes:
144
143
  error_message = i18n.t("Patron barcode already detected as missing")
@@ -147,108 +146,109 @@ class CirculationHelper:
147
146
  f"Item Barcode:{legacy_loan.item_barcode}"
148
147
  )
149
148
  return TransactionResult(False, False, "", error_message, error_message)
150
- req = httpx.post(url, headers=self.folio_client.okapi_headers, json=data, timeout=None)
151
- if req.status_code == 422:
152
- error_message_from_folio = json.loads(req.text)["errors"][0]["message"]
153
- stat_message = error_message_from_folio
154
- error_message = error_message_from_folio
155
- if "has the item status" in error_message_from_folio:
156
- stat_message = re.findall(
157
- r"(?<=has the item status\s).*(?=\sand cannot be checked out)",
158
- error_message_from_folio,
159
- )[0]
160
- error_message = (
161
- f"{stat_message} for item with barcode {legacy_loan.item_barcode}"
162
- )
163
- return TransactionResult(
164
- False,
165
- True,
166
- None,
167
- error_message_from_folio,
168
- stat_message,
169
- )
170
- elif "No item with barcode" in error_message_from_folio:
171
- error_message = f"No item with barcode {legacy_loan.item_barcode} in FOLIO"
172
- stat_message = "Item barcode not in FOLIO"
173
- self.missing_item_barcodes.add(legacy_loan.item_barcode)
174
- return TransactionResult(
175
- False,
176
- False,
177
- None,
178
- error_message_from_folio,
179
- stat_message,
180
- )
181
-
182
- elif "find user with matching barcode" in error_message_from_folio:
183
- self.missing_patron_barcodes.add(legacy_loan.patron_barcode)
184
- error_message = f"No patron with barcode {legacy_loan.patron_barcode} in FOLIO"
185
- stat_message = i18n.t("Patron barcode not in FOLIO")
186
- return TransactionResult(
187
- False,
188
- False,
189
- None,
190
- error_message_from_folio,
191
- stat_message,
192
- )
193
- elif "Cannot check out item that already has an open" in error_message_from_folio:
194
- return TransactionResult(
195
- False,
196
- False,
197
- None,
198
- error_message_from_folio,
199
- error_message_from_folio,
200
- )
201
- elif "Item is already checked out" in error_message_from_folio:
202
- return TransactionResult(
203
- False,
204
- True,
205
- None,
206
- error_message_from_folio,
207
- error_message_from_folio,
208
- )
209
- logging.error(
210
- f"{error_message} "
211
- f"Patron barcode: {legacy_loan.patron_barcode} "
212
- f"Item Barcode:{legacy_loan.item_barcode}"
149
+ loan = self.folio_client.folio_post(path, data)
150
+ stats = "Successfully checked out by barcode"
151
+ logging.debug(
152
+ "%s (item barcode %s}) in %ss",
153
+ stats,
154
+ legacy_loan.item_barcode,
155
+ f"{(time.time() - t0_function):.2f}",
156
+ )
157
+ return TransactionResult(True, False, loan, "", stats)
158
+ except FolioValidationError as fve:
159
+ error_message_from_folio = self.folio_client.handle_json_response(fve.response)[
160
+ "errors"
161
+ ][0]["message"]
162
+ stat_message = error_message_from_folio
163
+ error_message = error_message_from_folio
164
+ if "has the item status" in error_message_from_folio:
165
+ stat_message = re.findall(
166
+ r"(?<=has the item status\s).*(?=\sand cannot be checked out)",
167
+ error_message_from_folio,
168
+ )[0]
169
+ error_message = f"{stat_message} for item with barcode {legacy_loan.item_barcode}"
170
+ return TransactionResult(
171
+ False,
172
+ True,
173
+ None,
174
+ error_message_from_folio,
175
+ stat_message,
213
176
  )
214
- self.migration_report.add("Details", stat_message)
177
+ elif "No item with barcode" in error_message_from_folio:
178
+ error_message = f"No item with barcode {legacy_loan.item_barcode} in FOLIO"
179
+ stat_message = "Item barcode not in FOLIO"
180
+ self.missing_item_barcodes.add(legacy_loan.item_barcode)
215
181
  return TransactionResult(
216
- False, True, None, error_message, f"Check out error: {stat_message}"
182
+ False,
183
+ False,
184
+ None,
185
+ error_message_from_folio,
186
+ stat_message,
217
187
  )
218
- elif req.status_code == 201:
219
- stats = "Successfully checked out by barcode"
220
- logging.debug(
221
- "%s (item barcode %s}) in %ss",
222
- stats,
223
- legacy_loan.item_barcode,
224
- f"{(time.time() - t0_function):.2f}",
188
+
189
+ elif "find user with matching barcode" in error_message_from_folio:
190
+ self.missing_patron_barcodes.add(legacy_loan.patron_barcode)
191
+ error_message = f"No patron with barcode {legacy_loan.patron_barcode} in FOLIO"
192
+ stat_message = i18n.t("Patron barcode not in FOLIO")
193
+ return TransactionResult(
194
+ False,
195
+ False,
196
+ None,
197
+ error_message_from_folio,
198
+ stat_message,
225
199
  )
226
- return TransactionResult(True, False, json.loads(req.text), "", stats)
227
- elif req.status_code == 204:
228
- stats = "Successfully checked out by barcode"
229
- logging.debug(
230
- "%s (item barcode %s) %s",
231
- stats,
232
- legacy_loan.item_barcode,
233
- req.status_code,
200
+ elif "Cannot check out item that already has an open" in error_message_from_folio:
201
+ return TransactionResult(
202
+ False,
203
+ False,
204
+ None,
205
+ error_message_from_folio,
206
+ error_message_from_folio,
234
207
  )
235
- return TransactionResult(True, False, None, "", stats)
236
- else:
237
- req.raise_for_status()
238
- except HTTPError:
208
+ elif "Item is already checked out" in error_message_from_folio:
209
+ return TransactionResult(
210
+ False,
211
+ True,
212
+ None,
213
+ error_message_from_folio,
214
+ error_message_from_folio,
215
+ )
216
+ logging.error(
217
+ f"{error_message} "
218
+ f"Patron barcode: {legacy_loan.patron_barcode} "
219
+ f"Item Barcode:{legacy_loan.item_barcode}"
220
+ )
221
+ self.migration_report.add("Details", stat_message)
222
+ return TransactionResult(
223
+ False, True, None, error_message, f"Check out error: {stat_message}"
224
+ )
225
+ except FolioClientError as fce:
239
226
  logging.exception(
240
227
  "%s\tPOST FAILED %s\n\t%s\n\t%s",
241
- req.status_code,
242
- url,
228
+ fce.response.status_code,
229
+ fce.request.url,
243
230
  json.dumps(data),
244
- req.text,
231
+ fce.response.text,
245
232
  )
246
233
  return TransactionResult(
247
234
  False,
248
235
  False,
249
236
  None,
250
237
  "5XX",
251
- i18n.t("Failed checkout http status %{code}", code=req.status_code),
238
+ i18n.t("Failed checkout http status %{code}", code=fce.response.status_code),
239
+ )
240
+ except FolioConnectionError as fce:
241
+ logging.exception(
242
+ "Connection error\tPOST FAILED %s\n\t%s\n\t%s",
243
+ fce.request.url,
244
+ json.dumps(data),
245
+ )
246
+ return TransactionResult(
247
+ False,
248
+ False,
249
+ None,
250
+ "Connection error",
251
+ i18n.t("Connection error during checkout"),
252
252
  )
253
253
 
254
254
  @staticmethod
@@ -257,7 +257,6 @@ class CirculationHelper:
257
257
  ):
258
258
  try:
259
259
  path = "/circulation/requests"
260
- url = f"{folio_client.gateway_url}{path}"
261
260
  data = legacy_request.serialize()
262
261
  data["requestProcessingParameters"] = {
263
262
  "overrideBlocks": {
@@ -269,21 +268,31 @@ class CirculationHelper:
269
268
  "comment": "Migrated from legacy system",
270
269
  }
271
270
  }
272
- req = httpx.post(url, headers=folio_client.okapi_headers, json=data, timeout=None)
273
- logging.debug(f"POST {req.status_code}\t{url}\t{json.dumps(data)}")
274
- if str(req.status_code) == "422":
275
- message = json.loads(req.text)["errors"][0]["message"]
276
- logging.error(f"{message}")
271
+ _ = folio_client.folio_post(path, data)
272
+ logging.debug(f"POST {path}\t{json.dumps(data)}")
273
+ logging.info(
274
+ "%s Successfully created %s",
275
+ HTTPStatus.OK,
276
+ legacy_request.request_type,
277
+ )
278
+ return True
279
+ except FolioValidationError as fve:
280
+ message = folio_client.handle_json_response(fve.response)["errors"][0]["message"]
281
+ logging.error(message)
282
+ migration_report.add_general_statistics(message)
283
+ return False
284
+ except (FolioConnectionError, FolioClientError) as fce:
285
+ if client_response := getattr(fce, "response", None):
286
+ message = (
287
+ f"HTTP {client_response.status_code} Error creating request: "
288
+ f"{client_response.text}"
289
+ )
290
+ logging.error(message)
277
291
  migration_report.add_general_statistics(message)
278
- return False
279
292
  else:
280
- req.raise_for_status()
281
- logging.info(
282
- "%s Successfully created %s",
283
- req.status_code,
284
- legacy_request.request_type,
285
- )
286
- return True
293
+ logging.error(f"Connection error creating request: {fce}")
294
+ migration_report.add_general_statistics("Connection error creating request")
295
+ return False
287
296
  except Exception as exception:
288
297
  logging.error(exception, exc_info=True)
289
298
  migration_report.add("Details", exception)
@@ -7,10 +7,10 @@ class InsensitiveDictReader(csv.DictReader):
7
7
  # spaces and to lower case.
8
8
  @property
9
9
  def fieldnames(self):
10
- return [field.strip().lower() for field in csv.DictReader.fieldnames.fget(self)] # type: ignore
10
+ return [field.strip().lower() for field in csv.DictReader.fieldnames.fget(self)] # type: ignore
11
11
 
12
12
  def next(self):
13
- return InsensitiveDict(csv.DictReader.next(self)) # type: ignore
13
+ return InsensitiveDict(csv.DictReader.next(self)) # type: ignore
14
14
 
15
15
 
16
16
  class InsensitiveDict(dict):