ocrd 3.7.0__py3-none-any.whl → 3.8.0__py3-none-any.whl

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 (34) hide show
  1. ocrd/cli/network.py +2 -0
  2. ocrd/cli/resmgr.py +29 -65
  3. ocrd/constants.py +0 -2
  4. ocrd/processor/base.py +6 -16
  5. ocrd/processor/builtin/dummy/ocrd-tool.json +25 -0
  6. ocrd/processor/builtin/merge_processor.py +131 -0
  7. ocrd/processor/builtin/param_command_header2unordered.json +7 -0
  8. ocrd/processor/builtin/param_command_heading2unordered.json +7 -0
  9. ocrd/processor/builtin/param_command_lines2orientation.json +6 -0
  10. ocrd/processor/builtin/param_command_page-update-version.json +5 -0
  11. ocrd/processor/builtin/param_command_transkribus-to-prima.json +8 -0
  12. ocrd/processor/builtin/shell_processor.py +128 -0
  13. ocrd/resource_manager.py +213 -124
  14. {ocrd-3.7.0.dist-info → ocrd-3.8.0.dist-info}/METADATA +22 -3
  15. {ocrd-3.7.0.dist-info → ocrd-3.8.0.dist-info}/RECORD +33 -25
  16. {ocrd-3.7.0.dist-info → ocrd-3.8.0.dist-info}/entry_points.txt +2 -0
  17. ocrd_models/ocrd_agent.py +3 -3
  18. ocrd_network/__init__.py +1 -0
  19. ocrd_network/cli/__init__.py +2 -0
  20. ocrd_network/cli/resmgr_server.py +23 -0
  21. ocrd_network/constants.py +3 -0
  22. ocrd_network/logging_utils.py +5 -0
  23. ocrd_network/resource_manager_server.py +182 -0
  24. ocrd_network/runtime_data/connection_clients.py +1 -1
  25. ocrd_network/runtime_data/hosts.py +43 -16
  26. ocrd_network/runtime_data/network_agents.py +15 -1
  27. ocrd_utils/__init__.py +5 -1
  28. ocrd_utils/constants.py +5 -0
  29. ocrd_utils/os.py +141 -61
  30. ocrd_validators/ocrd_tool.schema.yml +7 -4
  31. ocrd/resource_list.yml +0 -61
  32. {ocrd-3.7.0.dist-info → ocrd-3.8.0.dist-info}/LICENSE +0 -0
  33. {ocrd-3.7.0.dist-info → ocrd-3.8.0.dist-info}/WHEEL +0 -0
  34. {ocrd-3.7.0.dist-info → ocrd-3.8.0.dist-info}/top_level.txt +0 -0
@@ -1,20 +1,19 @@
1
1
  ocrd/__init__.py,sha256=ZswMVmlqFhAEIzMR3my6IKPq9XLH21aDPC_m_8Jh4dA,1076
2
- ocrd/constants.py,sha256=6dn3mG54WqHsKInmLZp4kJjNqqPtBoFoSuLUuRbOps0,740
2
+ ocrd/constants.py,sha256=REPY-y28MMsrTWBNB4oOsvX3W06Xr2fvtv9wuWH9oAI,633
3
3
  ocrd/mets_server.py,sha256=LbZ0U2_o0W7cWO639U7E816dXabro8-8yHGX0quvHn4,22304
4
4
  ocrd/ocrd-all-tool.json,sha256=EYXmMzP68p3KzL8nUZ16TCX2chQzKkAeISvuXqI_yIw,2094
5
5
  ocrd/resolver.py,sha256=7uwHRxaK8YMdKHe_a2dfrcNwL6UhQRJRVBrIX7GST7Q,15443
6
- ocrd/resource_list.yml,sha256=82-PiqkZnka1kTj3MQqNn4wXWKHHtoFchsQuetWuqFs,2633
7
- ocrd/resource_manager.py,sha256=kIWDoKxWH4IJE1gcoTcCRQjYjieCqiQclyuyF6Y9b8A,16813
6
+ ocrd/resource_manager.py,sha256=2wo3JSCYE1oA0VgI8H901IsC-fnx6vRJ5qSMFgYNorE,20664
8
7
  ocrd/task_sequence.py,sha256=r4e4iaP9AXzTL2xQZpfYnHuFXty5pE-ym3gIyUz1aJc,7180
9
8
  ocrd/workspace.py,sha256=UL_gX0KA-MmpayBl9KGYTfcl-1Canj8S991G9RHhu70,65216
10
9
  ocrd/workspace_backup.py,sha256=aUOnYeJ-nWu-Zve27B0cYd9ZtBkmQX4F4Wim2UcrR5I,3624
11
10
  ocrd/workspace_bagger.py,sha256=4viSQoWteW0V4B_blB6asJXd4-qniGGJyCPfKnrsyrY,12054
12
11
  ocrd/cli/__init__.py,sha256=-n2jpGBZs_OMpI31E7CljGVdoFxDhgCAYwibcl_vp1Q,2838
13
12
  ocrd/cli/bashlib.py,sha256=sEpTKbqM5DEo6838Ki5aFU8QsokA2SqQ841gcBu7M5M,1148
14
- ocrd/cli/network.py,sha256=iQ0AhQRGvIFyJY9RBArUiA_wuz7IfNKvU4L8KpVggnY,530
13
+ ocrd/cli/network.py,sha256=HA-JeyedsZksTQzuDoSBwyxEHIyIlc7oCmvNNDMA4vA,615
15
14
  ocrd/cli/ocrd_tool.py,sha256=kB3Y3tj7Fpz6Ts4KgVlznhXpAx8gCDvJTnO39j8SGL4,7679
16
15
  ocrd/cli/process.py,sha256=yfhBSYmuY5k2AccKwiNvG9hCDx1coYyWjq9BBwYaL3Y,1234
17
- ocrd/cli/resmgr.py,sha256=b1TMZ3D0d50RR9XjQdPil3sfaoTVAso8LjHXZ9P7WfI,10109
16
+ ocrd/cli/resmgr.py,sha256=7hRRi8EryQwakRdZgguee3ercA5_T48BKGWWfgAVfzM,8072
18
17
  ocrd/cli/validate.py,sha256=P8jrzAnoU-5TUjLNA7s_ZMY2Krw5Y-SVIZPhdOk25cw,5931
19
18
  ocrd/cli/workspace.py,sha256=0UzKN7vvD0n5wwxldzLHOlikDDIyiBiV1PuTOKCnnnE,41279
20
19
  ocrd/cli/zip.py,sha256=3HMUbVsPTK3SRuF5oZnCZLjoqXJK-AYpA-rMqenY858,5965
@@ -24,19 +23,26 @@ ocrd/decorators/mets_find_options.py,sha256=8fiSdk-415o6-iBPB2T9He_v52qE8cTj3cCn
24
23
  ocrd/decorators/ocrd_cli_options.py,sha256=Bemkq3V3QkOI3nNqGzphaNW7gjU9vNN-M5F2DvxvioM,2479
25
24
  ocrd/decorators/parameter_option.py,sha256=TnCIcV9L5oAnI1Ew2TyFzo5FAwiIzWl2pn8oaD9jfEU,1056
26
25
  ocrd/processor/__init__.py,sha256=39ymNwYRdc-b_OJzzKmWCvo2ga3KdsGSYDHE1Hzkn_w,274
27
- ocrd/processor/base.py,sha256=TOUy_s14aM-09HSSzihgCeTOSHRpTO4U4bze7QUSDwg,60382
26
+ ocrd/processor/base.py,sha256=DxBsRn8VLsfNvc9_2BU0KxUv4t9XtHSSu9uiabxn8Nk,59850
28
27
  ocrd/processor/helpers.py,sha256=4lR_QvZsxvh7f8_uK9YzdHP5-hvFU4qqYM_Cu_k41KI,10937
29
28
  ocrd/processor/ocrd_page_result.py,sha256=qo9pGV4r9S5--NAq5clIJOfs4b1vavoDOTbDqAEAAKA,507
30
29
  ocrd/processor/builtin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
30
  ocrd/processor/builtin/dummy_processor.py,sha256=SmMRtN0w88kBU24654ThT-yf84SFsFW4BOcmwsDDWdc,3533
32
31
  ocrd/processor/builtin/filter_processor.py,sha256=9mbMq_XTJa8wrlbNdf46GUMNdjedz-enxafsCrnNhEo,4295
32
+ ocrd/processor/builtin/merge_processor.py,sha256=UvYgB73Y9lzJSMMNaVO6nk3rAcONHvQ-E-XAhaseZno,4655
33
+ ocrd/processor/builtin/param_command_header2unordered.json,sha256=K6xEcDXc3Qsaxt96wdISK22UxHf517O-E8JHryDwAfE,307
34
+ ocrd/processor/builtin/param_command_heading2unordered.json,sha256=nBenDJYlV-POfoM2R6izxcA5pW3rz-czGaoKudj3OTY,309
35
+ ocrd/processor/builtin/param_command_lines2orientation.json,sha256=2cGv5fXqAVAUyP8K1IT6vrSLCGhAtdXAvVxdSfjp8KQ,282
36
+ ocrd/processor/builtin/param_command_page-update-version.json,sha256=4JsXEltEOG89Q8PT8eBxQXnxp3Mez3EsRz4GfshjJEY,267
37
+ ocrd/processor/builtin/param_command_transkribus-to-prima.json,sha256=AvPNNS5uBmh69i6irRhIdHN9gE28yN9ufV7jfpFAeME,472
38
+ ocrd/processor/builtin/shell_processor.py,sha256=aWsB_m7o4ypG1DBAE0sNMFnaw9ptONqchLLl06KgTEo,5888
33
39
  ocrd/processor/builtin/dummy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- ocrd/processor/builtin/dummy/ocrd-tool.json,sha256=NgMAXN1AQpGk4Ss73ThDY4QyFPKhj54qcrdeCGwTb10,2339
40
+ ocrd/processor/builtin/dummy/ocrd-tool.json,sha256=t_M3HABw7k_Ufi1L9Mr4t3LSCRnu0HH8-fvEs3u2PQY,3487
35
41
  ocrd_modelfactory/__init__.py,sha256=sjAwPwDzetvPHdV6nPquHtMdFUBYRmo1P-VKER9YCWM,4404
36
42
  ocrd_models/__init__.py,sha256=A0aj0mOraNb-xfiUueACdoaqISnp0qH-F49nTJg2vCs,380
37
43
  ocrd_models/constants.py,sha256=R7-jOGabFd8HP0qRWfTMk0RcUmdwN-jhmDVbUW_QfU4,6961
38
44
  ocrd_models/mets-empty.xml,sha256=dFixfbxSXrgjZx9BfdIKWHX-khNmp7dNYaFe2qQSwCY,1203
39
- ocrd_models/ocrd_agent.py,sha256=fY3BQE7otU9KLHaKC9L7BjuU1PIXXTy17V-OAW8LzjI,5609
45
+ ocrd_models/ocrd_agent.py,sha256=Nm0XDNCmWZ8O3xsXaY-WmEghttXmh90UKmAObCL99IY,5617
40
46
  ocrd_models/ocrd_exif.py,sha256=HSLPn_WBDRIlMtKNYilLHm8WjX-b14HgnqT_KfzjS_0,4680
41
47
  ocrd_models/ocrd_file.py,sha256=9-mfDb91RVy3p9rKryl-C39P4Of6Rb8OZBuxAee4VrI,9723
42
48
  ocrd_models/ocrd_mets.py,sha256=lz9mlDq9A9UmZDoN8lh5XRnBzdAtLLZywDZSbyZPS84,50905
@@ -46,24 +52,26 @@ ocrd_models/ocrd_xml_base.py,sha256=iOnDl2zBNhN-Q4moLWiFkSqXvfRzxE5wbp5Tjsu1W6A,
46
52
  ocrd_models/report.py,sha256=CX-t9ZDi2VmAy8M1Azsh83UsvE_f5pMeEC7tPaA-ztU,2021
47
53
  ocrd_models/utils.py,sha256=A-H11ZJ65ZjH4DPK9s_Yz6JtA9fbTQ2jY-__9s7Hrg8,2320
48
54
  ocrd_models/xpath_functions.py,sha256=VM2f9hl8ja4NrDOEQRSYdx7GewwAxfoyGMDjqjgA_7g,1439
49
- ocrd_network/__init__.py,sha256=w6uj3joSsg5NAzQPDsHkuwVjHXukO_JrvcAX07cBlHA,317
55
+ ocrd_network/__init__.py,sha256=NWlSgXi7z45ow37AmITxfCB1d-L39rO8ttyxNJ-z8G0,376
50
56
  ocrd_network/client.py,sha256=pL-g79cQgulXyGYgLOh--oxl1hZEMu48PTbuvMW1jIE,3007
51
57
  ocrd_network/client_utils.py,sha256=Ne1a0fteb-TBuc0EAD6X_fh2RAl4hmPt2oluhpB28iU,5371
52
- ocrd_network/constants.py,sha256=iLHPX5H3g6QKhqIPXCNRILVG7eFUIWuuiBWKp0_dBLw,1812
58
+ ocrd_network/constants.py,sha256=XyRYjFO38yIBD6s1wsA-z6V16tBmbUw4LXlFkj-tQC8,1943
53
59
  ocrd_network/database.py,sha256=-SddvaMLKn0pjdONyvWmjxfPJd6viedAIp6Lj1sU1Zs,10705
54
- ocrd_network/logging_utils.py,sha256=Ez5f_BRp0SiW2sP71yTT7zOJmuMzHYPLNWObq5fBLCw,2188
60
+ ocrd_network/logging_utils.py,sha256=hXwS46FzY_HTh92DgnxTuARxj8C18bOBmFKVrvBlUgc,2409
55
61
  ocrd_network/param_validators.py,sha256=Jl1VwiPPKJ50k-xEHLdvW-1QDOkJHCiMz4k9Ipqm-Uc,1489
56
62
  ocrd_network/process_helpers.py,sha256=t2qltUpRefzLwdSGsiUEOGYO4Pz2OH7arpgjmCAeXMU,3086
57
63
  ocrd_network/processing_server.py,sha256=z21DvRleEeo0hkpc1-2z0jLKuf5WSipL95MVEns8eJE,38457
58
64
  ocrd_network/processing_worker.py,sha256=5AtvIhfcePzltKj4SElh7Aj9zlUOEiMVPTjtXuFSbT8,12659
65
+ ocrd_network/resource_manager_server.py,sha256=Ihz2g9uhkPSJee9GL7485DFC4cORhro4JQI6QzHoUA4,7255
59
66
  ocrd_network/server_cache.py,sha256=orfAMw3LwUnduRHFAB6MpfoORTDoPV4ntSdAcQHBOyI,13148
60
67
  ocrd_network/server_utils.py,sha256=Lxby62gHvrSbHgpWXvyZGdsWajp2TFzyxjHdMZWBESk,10229
61
68
  ocrd_network/tcp_to_uds_mets_proxy.py,sha256=yRW-O6ihd31gf7xqQBIBb_ZQQgqisMyOdRI216ehq_A,3160
62
69
  ocrd_network/utils.py,sha256=yE-nV_sv171tPp7weIFOxYw6HJlxvGBmrS8b1rIHS7c,6760
63
- ocrd_network/cli/__init__.py,sha256=7dzZLbGHaqMGToNZhll-q87Jh4UP6NSuvxbVuUOIkqY,228
70
+ ocrd_network/cli/__init__.py,sha256=VBjjXcn-2O5gerqE6UdNfS-EkVFEVPQFHylsn8F9kfY,317
64
71
  ocrd_network/cli/client.py,sha256=H5fiJhBqbFn4_B2p3V20GejGTIYO-mNglh3y5nzUGhs,10350
65
72
  ocrd_network/cli/processing_server.py,sha256=NsuI0f9h4KDwe39YugmHo5cJ_29chcLLQ7DThKfPO7s,770
66
73
  ocrd_network/cli/processing_worker.py,sha256=ZuaCkbKV_WKJV7cGOjZ6RLrjjppymnwNCiznFMlclAg,1897
74
+ ocrd_network/cli/resmgr_server.py,sha256=sc0VX_RehTbg8Qp7ht_DvVqsrdL5b9Zw3bBgWcAD13A,826
67
75
  ocrd_network/models/__init__.py,sha256=eVYMZaktzlyHKx-zI7GLYyRlZd3Vi_lNgsqSSFwqb6U,475
68
76
  ocrd_network/models/job.py,sha256=9bwp8DFoRH96WnRpkDV3XRfXCBiupzK6WXjqPsTcvLg,4440
69
77
  ocrd_network/models/messages.py,sha256=OUDTjUiaATStsSAHCEDilUhBSruPsjpBtIBsllqN2Z0,672
@@ -78,20 +86,20 @@ ocrd_network/rabbitmq_utils/ocrd_messages.py,sha256=wwzfMWbXmOFo_nd32_XySCso91_U
78
86
  ocrd_network/rabbitmq_utils/publisher.py,sha256=mw4XQQhRE1xUQVgEUseyG845iIgVO-9GdGwNH6nUFms,2433
79
87
  ocrd_network/runtime_data/__init__.py,sha256=PnWuuagElbkTzGtPWQEk5wlFtDxqT7B48S0Zrgt8H68,320
80
88
  ocrd_network/runtime_data/config_parser.py,sha256=Vr0FbsqmsoiuhDgZ7KFdeFZj9JvUulcOS2PCRFQQNHY,2364
81
- ocrd_network/runtime_data/connection_clients.py,sha256=DZyAvkNyMaIddGJs56s2pMP_fK-XWAtICxk1cjvkWYM,4207
89
+ ocrd_network/runtime_data/connection_clients.py,sha256=HKf_aSfwg11JeH6qiQXnqxbnvNgCAMRnVIpj30k93Bc,4207
82
90
  ocrd_network/runtime_data/deployer.py,sha256=j3tcauURZtu7MKcEIE9B5eMCMSYMbxhB8LmtK72Zk1c,5314
83
- ocrd_network/runtime_data/hosts.py,sha256=n0azh_1XBc8-F9GtX-8q61iFzfxZjtjG-2D8qroihGA,7233
84
- ocrd_network/runtime_data/network_agents.py,sha256=UKcAO1lMZkXgmhMbltBnRo1j7QVSbfDTxA52YrNqP2Y,3891
91
+ ocrd_network/runtime_data/hosts.py,sha256=P1bLh1NjgL-ajgP-VhGCACvy6rgJ5nZhGKsHaldzatk,8921
92
+ ocrd_network/runtime_data/network_agents.py,sha256=wwN7IJei4UdlyfhjvdmB5TB4O0Gn8icSkArJe-suvAY,4523
85
93
  ocrd_network/runtime_data/network_services.py,sha256=5aH3RNGCi1fBuSdRp_Xz0MzyD_FmnvPnaBYAiYY3gp4,7891
86
- ocrd_utils/__init__.py,sha256=gcO26xJ6dIUtJIvAr8wOe3CM4b7Revn07-DwEureoEc,5973
94
+ ocrd_utils/__init__.py,sha256=Cl_lrZxjXuTZ_me4I_lpaFNTpSdacWhQetOtHdrkUsU,6057
87
95
  ocrd_utils/config.py,sha256=Oe8JBGb8r4z274XNWcdMV-GApzxmAYO8hHmbAV5bXf8,12609
88
- ocrd_utils/constants.py,sha256=ImbG1d8t2MW3uuFi-mN6aY90Zn74liAKZBKlfuKN86w,3278
96
+ ocrd_utils/constants.py,sha256=6lqMLeJdkFBlvGVmGjcExWbRKzNU6QT0kADBb5BkcBc,3464
89
97
  ocrd_utils/deprecate.py,sha256=luAqGWUSF-9DHmTd2lDiQoQPA5SrJazdoDPQYQ6A7Z4,1029
90
98
  ocrd_utils/image.py,sha256=tG5WnNtrrvGjm2-r6NVs1Jm7z8fee3MuLKotAD6C2RU,24818
91
99
  ocrd_utils/introspect.py,sha256=LPhgcUuoicQcURDCWlCpSdbfVyxID5vmQPXJ9vzuYV0,1977
92
100
  ocrd_utils/logging.py,sha256=-cCi_9kIzmLUixfnDcx2jq9IQuwMqrU-71RJhKOQilQ,7929
93
101
  ocrd_utils/ocrd_logging.conf,sha256=JlWmA_5vg6HnjPGjTC4mA5vFHqmnEinwllSTiOw5CCo,3473
94
- ocrd_utils/os.py,sha256=QEOramsUmBDzZxslPMZhfTviPr7EnopXKEgNc5zwwTs,9817
102
+ ocrd_utils/os.py,sha256=Fmy-Q4OMGlbHLMH9jhPRAxRN5curyEXrYoWJxztIl1w,13756
95
103
  ocrd_utils/str.py,sha256=4P0MdX0LCTqDTnsi_y5wNOBXW_TuTFANF7NYRXjo4x0,10136
96
104
  ocrd_validators/__init__.py,sha256=ZFc-UqRVBk9o1YesZFmr9lOepttNJ_NKx1Zdb7g_YsU,972
97
105
  ocrd_validators/bagit-profile.yml,sha256=sdQJlSi7TOn1E9WYMOZ1shewJ-i_nPaKmsAFkh28TGY,1011
@@ -101,7 +109,7 @@ ocrd_validators/message_processing.schema.yml,sha256=HL7o96-7ejslVMXcp16sbo5IjfU
101
109
  ocrd_validators/message_result.schema.yml,sha256=G6vt_JgIU7OGSaHj-2Jna6KWQ3bFWol5tnBArWEiVjM,681
102
110
  ocrd_validators/mets.xsd,sha256=0Wrs9bObn0n-yEEIWyguIcUUuuP6KMEjD4I_p1_UlwY,138290
103
111
  ocrd_validators/ocrd_network_message_validator.py,sha256=oafNWOjieBmTHFfYeCtyFFpW1gI0lDT6ycRr5Kvmfq0,561
104
- ocrd_validators/ocrd_tool.schema.yml,sha256=BQkRIRDbn9B8gFeVxz_EpNdleh_x2dCtIpJEC4HqFHw,10125
112
+ ocrd_validators/ocrd_tool.schema.yml,sha256=fDNr-QdEOBtYbz8aHmjdOUirPBKr3vfLUDtC88gu75U,10231
105
113
  ocrd_validators/ocrd_tool_validator.py,sha256=0DWuyyOSbdbrrQ5kEfWZv_qp5rSmLzmFMUKcPGfCBgM,749
106
114
  ocrd_validators/ocrd_zip_validator.py,sha256=t-cYIZ5llZSQ2EspFzm0m-FajkLRfAFTISmXe27wMtA,3720
107
115
  ocrd_validators/page.xsd,sha256=abQ8C3gRLPMFm8lH62aTCfvTIWI23TpgEDcaW9YCt7I,85770
@@ -115,9 +123,9 @@ ocrd_validators/xlink.xsd,sha256=8fW7YAMWXN2PbB_MMvj9H5ZeFoEBDzuYBtlGC8_6ijw,318
115
123
  ocrd_validators/xsd_mets_validator.py,sha256=YgiuNtwNDtn3LuvdFFscnmsGREF_wQ4wtA76yE2Iljw,469
116
124
  ocrd_validators/xsd_page_validator.py,sha256=ggt-nmaz-DDyAPwm3ZMVvtChuV2BJ2ZEEbWpePL9vTk,469
117
125
  ocrd_validators/xsd_validator.py,sha256=ahJo_oVvTK_JB0Cu4CkMC8l_gbzsyW91AxGtelMjqrg,2115
118
- ocrd-3.7.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
119
- ocrd-3.7.0.dist-info/METADATA,sha256=yCPxM32p95ODwi46-eVJxTjxCheoiuxOZujpKYmEzIA,10340
120
- ocrd-3.7.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
121
- ocrd-3.7.0.dist-info/entry_points.txt,sha256=4hcJ2LkK_OlIabHnKgFit35Ap7b5Lz1Gb4hzkxV0Kiw,152
122
- ocrd-3.7.0.dist-info/top_level.txt,sha256=pUgiN42t4KXC5rvpi6V8atza31XP4SCznXpXlVlvomM,75
123
- ocrd-3.7.0.dist-info/RECORD,,
126
+ ocrd-3.8.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
127
+ ocrd-3.8.0.dist-info/METADATA,sha256=ieqTzchmz7OXQV6vlize7XzWDzWEk5ypLXtn4VTduHY,11396
128
+ ocrd-3.8.0.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
129
+ ocrd-3.8.0.dist-info/entry_points.txt,sha256=CI-NoDR1BYmsuAsJmPAn4NrN9guzdedHGUbC8QSmdGs,266
130
+ ocrd-3.8.0.dist-info/top_level.txt,sha256=pUgiN42t4KXC5rvpi6V8atza31XP4SCznXpXlVlvomM,75
131
+ ocrd-3.8.0.dist-info/RECORD,,
@@ -1,4 +1,6 @@
1
1
  [console_scripts]
2
2
  ocrd = ocrd.cli:cli
3
+ ocrd-command = ocrd.processor.builtin.shell_processor:cli
3
4
  ocrd-dummy = ocrd.processor.builtin.dummy_processor:cli
4
5
  ocrd-filter = ocrd.processor.builtin.filter_processor:cli
6
+ ocrd-merge = ocrd.processor.builtin.merge_processor:cli
ocrd_models/ocrd_agent.py CHANGED
@@ -43,7 +43,7 @@ class OcrdAgent():
43
43
  self.otherrole = otherrole
44
44
  self.notes = notes
45
45
 
46
- def __str__(self):
46
+ def __repr__(self):
47
47
  """
48
48
  String representation
49
49
  """
@@ -142,7 +142,7 @@ class OcrdAgent():
142
142
  """
143
143
  el_notes = self._el.findall(TAG_METS_NOTE)
144
144
  if el_notes is not None:
145
- return [(note.attrib, note.text)
145
+ return [(dict(note.attrib), note.text)
146
146
  for note in el_notes]
147
147
 
148
148
  @notes.setter
@@ -190,7 +190,7 @@ class ClientSideOcrdAgent():
190
190
  self.otherrole = otherrole
191
191
  self.notes = notes
192
192
 
193
- def __str__(self):
193
+ def __repr__(self):
194
194
  props = ', '.join([
195
195
  '='.join([k, getattr(self, k) if getattr(self, k) else '---'])
196
196
  for k in ['type', 'othertype', 'role', 'otherrole', 'name']
ocrd_network/__init__.py CHANGED
@@ -3,4 +3,5 @@ from .constants import JobState
3
3
  from .processing_server import ProcessingServer
4
4
  from .processing_worker import ProcessingWorker
5
5
  from .param_validators import DatabaseParamType, ServerAddressParamType, QueueServerParamType
6
+ from .resource_manager_server import ResourceManagerServer
6
7
  from .server_cache import CacheLockedPages, CacheProcessingRequests
@@ -1,9 +1,11 @@
1
1
  from .client import client_cli
2
2
  from .processing_server import processing_server_cli
3
3
  from .processing_worker import processing_worker_cli
4
+ from .resmgr_server import resource_manager_server_cli
4
5
 
5
6
  __all__ = [
6
7
  'client_cli',
7
8
  'processing_server_cli',
8
9
  'processing_worker_cli',
10
+ 'resource_manager_server_cli'
9
11
  ]
@@ -0,0 +1,23 @@
1
+ import click
2
+ from ocrd_network import ResourceManagerServer, ServerAddressParamType
3
+
4
+
5
+ @click.command('resmgr-server')
6
+ @click.option('-a', '--address',
7
+ help='The URL of the OCR-D resource manager server, format: host:port',
8
+ type=ServerAddressParamType(),
9
+ required=True)
10
+ def resource_manager_server_cli(address: str):
11
+ """
12
+ Start standalone REST API OCR-D Resource Manager Server
13
+ """
14
+ try:
15
+ # Note, the address is already validated with the type field
16
+ host, port = address.split(':')
17
+ resource_manager_server = ResourceManagerServer(
18
+ host = host,
19
+ port = int(port)
20
+ )
21
+ resource_manager_server.start()
22
+ except Exception as e:
23
+ raise Exception("OCR-D Resource Manager Server has failed with error") from e
ocrd_network/constants.py CHANGED
@@ -10,6 +10,8 @@ OCRD_ALL_TOOL_JSON = "ocrd-all-tool.json"
10
10
  # Used as a placeholder to lock all pages when no page_id is specified
11
11
  SERVER_ALL_PAGES_PLACEHOLDER = "all_pages"
12
12
 
13
+ # TODO: Make this more configurable
14
+ RESOURCE_MANAGER_SERVER_PORT = 45555
13
15
 
14
16
  class StrEnum(str, Enum):
15
17
  def __str__(self):
@@ -48,6 +50,7 @@ class NetworkLoggingDirs(StrEnum):
48
50
  PROCESSING_JOBS = "processing_jobs"
49
51
  PROCESSING_SERVERS = "processing_servers"
50
52
  PROCESSING_WORKERS = "processing_workers"
53
+ RESOURCE_MANAGER_SERVERS = "resource_manager_servers"
51
54
 
52
55
 
53
56
  class ServerApiTags(StrEnum):
@@ -56,3 +56,8 @@ def get_processing_server_logging_file_path(pid: int) -> Path:
56
56
  def get_processing_worker_logging_file_path(processor_name: str, pid: int) -> Path:
57
57
  log_file: str = f"worker.{pid}.{processor_name}.log"
58
58
  return Path(get_root_logging_dir(NetworkLoggingDirs.PROCESSING_WORKERS), log_file)
59
+
60
+
61
+ def get_resource_manager_server_logging_file_path(pid: int) -> Path:
62
+ log_file: str = f"resource_manager_server.{pid}.log"
63
+ return Path(get_root_logging_dir(NetworkLoggingDirs.RESOURCE_MANAGER_SERVERS), log_file)
@@ -0,0 +1,182 @@
1
+ from datetime import datetime
2
+ from os import getpid
3
+ from shutil import which
4
+ from typing import Any
5
+ from uvicorn import run as uvicorn_run
6
+ from fastapi import APIRouter, FastAPI, HTTPException, status
7
+
8
+ from ocrd import OcrdResourceManager
9
+ from ocrd_utils import getLogger, get_ocrd_tool_json, initLogging
10
+ from .logging_utils import configure_file_handler_with_formatter, get_resource_manager_server_logging_file_path
11
+
12
+
13
+ class ResourceManagerServer(FastAPI):
14
+ def __init__(self, host: str, port: int) -> None:
15
+ self.title = f"OCR-D Resource Manager Server"
16
+ super().__init__(
17
+ title=self.title,
18
+ on_startup=[self.on_startup],
19
+ on_shutdown=[self.on_shutdown],
20
+ description=self.title
21
+ )
22
+ initLogging()
23
+ self.log = getLogger("ocrd_network.resource_manager_server")
24
+ log_file = get_resource_manager_server_logging_file_path(pid=getpid())
25
+ configure_file_handler_with_formatter(self.log, log_file=log_file, mode="a")
26
+
27
+ self.resmgr_instance = OcrdResourceManager()
28
+
29
+ self.hostname = host
30
+ self.port = port
31
+
32
+ self.add_api_routes()
33
+
34
+ def start(self):
35
+ uvicorn_run(self, host=self.hostname, port=int(self.port))
36
+
37
+ async def on_startup(self):
38
+ self.log.info(f"Starting {self.title}")
39
+ pass
40
+
41
+ async def on_shutdown(self) -> None:
42
+ pass
43
+
44
+ def add_api_routes(self):
45
+ base_router = APIRouter()
46
+ base_router.add_api_route(
47
+ path="/",
48
+ endpoint=self.home_page,
49
+ methods=["GET"],
50
+ status_code=status.HTTP_200_OK,
51
+ summary="Get information about the OCR-D Resource Manager Server"
52
+ )
53
+ base_router.add_api_route(
54
+ path="/list_available",
55
+ endpoint=self.list_available_resources,
56
+ methods=["GET"],
57
+ status_code=status.HTTP_200_OK,
58
+ summary=""
59
+ )
60
+ base_router.add_api_route(
61
+ path="/list_installed",
62
+ endpoint=self.list_installed_resources,
63
+ methods=["GET"],
64
+ status_code=status.HTTP_200_OK,
65
+ summary=""
66
+ )
67
+ base_router.add_api_route(
68
+ path="/download",
69
+ endpoint=self.download_resource,
70
+ methods=["GET"],
71
+ status_code=status.HTTP_200_OK,
72
+ summary=""
73
+ )
74
+ self.include_router(base_router)
75
+
76
+ async def home_page(self):
77
+ message = f"The home page of the {self.title}"
78
+ json_message = {
79
+ "message": message,
80
+ "time": datetime.now().strftime("%Y-%m-%d %H:%M")
81
+ }
82
+ return json_message
83
+
84
+ async def list_available_resources(
85
+ self,
86
+ executable: Any = "ocrd-dummy",
87
+ dynamic: bool = True,
88
+ name: Any = None,
89
+ database: Any = None,
90
+ url: Any = None
91
+ ):
92
+ if executable == '*':
93
+ message = f"'*' is not an acceptable executable name! Try with a specific executable."
94
+ self.log.error(message)
95
+ raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=message)
96
+ result = self.resmgr_instance.list_available(executable, dynamic, name, database, url)
97
+ json_message = {
98
+ "result": result
99
+ }
100
+ return json_message
101
+
102
+ async def list_installed_resources(self, executable: Any = None):
103
+ if executable == '*':
104
+ message = f"'*' is not an acceptable executable name! Try with a specific executable."
105
+ self.log.error(message)
106
+ raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=message)
107
+ result = self.resmgr_instance.list_available(executable)
108
+ json_message = {
109
+ "result": result
110
+ }
111
+ return json_message
112
+
113
+ async def download_resource(
114
+ self,
115
+ executable: str = "ocrd-dummy",
116
+ name: Any = None,
117
+ location: Any = None,
118
+ any_url: str = '',
119
+ no_dynamic: bool = False,
120
+ resource_type: str = 'file',
121
+ path_in_archive: str = '.',
122
+ allow_uninstalled: bool = True,
123
+ overwrite: bool = True
124
+ ):
125
+ resmgr = OcrdResourceManager()
126
+ response = []
127
+ if executable == '*':
128
+ message = f"'*' is not an acceptable executable name! Try with a specific executable."
129
+ self.log.error(message)
130
+ raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=message)
131
+ if name == '*':
132
+ name = None
133
+ if executable and not which(executable):
134
+ if not allow_uninstalled:
135
+ message = (f"Executable '{executable}' is not installed. To download resources anyway, "
136
+ f"use the -a/--allow-uninstalled flag")
137
+ self.log.error(message)
138
+ raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=message)
139
+ else:
140
+ message = f"Executable '{executable}' is not installed, but downloading resources anyway."
141
+ self.log.info(message)
142
+ response.append(message)
143
+ reslist = resmgr.list_available(executable=executable, dynamic=not no_dynamic, name=name)
144
+ if not any(r[1] for r in reslist):
145
+ message = f"No resources {name} found in registry for executable {executable}"
146
+ self.log.info(message)
147
+ response.append(message)
148
+ if executable and name:
149
+ reslist = [(executable, [{
150
+ 'url': any_url or '???',
151
+ 'name': name,
152
+ 'type': resource_type,
153
+ 'path_in_archive': path_in_archive}]
154
+ )]
155
+ for this_executable, this_reslist in reslist:
156
+ resource_locations = get_ocrd_tool_json(this_executable)['resource_locations']
157
+ if not location:
158
+ location = resource_locations[0]
159
+ elif location not in resource_locations:
160
+ response.append(
161
+ f"The selected --location {location} is not in the {this_executable}'s resource search path, "
162
+ f"refusing to install to invalid location. Instead installing to: {resource_locations[0]}")
163
+ res_dest_dir = resmgr.build_resource_dest_dir(location=location, executable=this_executable)
164
+ for res_dict in this_reslist:
165
+ try:
166
+ fpath = resmgr.handle_resource(
167
+ res_dict=res_dict,
168
+ executable=this_executable,
169
+ dest_dir=res_dest_dir,
170
+ any_url=any_url,
171
+ overwrite=overwrite,
172
+ resource_type=resource_type,
173
+ path_in_archive=path_in_archive
174
+ )
175
+ if not fpath:
176
+ continue
177
+ except FileExistsError as exc:
178
+ response.append(str(exc))
179
+ usage = res_dict.get('parameter_usage', 'as-is')
180
+ response.append(f"Use in parameters as '{resmgr.parameter_usage(res_dict['name'], usage)}'")
181
+ json_message = { "result": response }
182
+ return json_message
@@ -36,7 +36,7 @@ class CustomDockerClient(DockerClient):
36
36
  raise ValueError("Both 'password' and 'keypath' provided - one must be provided")
37
37
  if ("password" not in kwargs) and ("keypath" not in kwargs):
38
38
  raise ValueError("Missing 'password' or 'keypath' - one must be provided")
39
- self.api = APIClient(base_url=f"ssh://{host}", use_ssh_client=True, version="1.41")
39
+ self.api = APIClient(base_url=f"ssh://{host}", use_ssh_client=True, version="auto")
40
40
  self.api.mount(
41
41
  prefix="http+docker://ssh", adapter=self.CustomSshHttpAdapter(base_url=f"ssh://{user}@{host}:22", **kwargs)
42
42
  )
@@ -1,9 +1,13 @@
1
1
  from logging import Logger
2
- from time import sleep
3
- from typing import Dict, List
2
+ from typing import Dict, List, Optional
4
3
 
5
- from .connection_clients import create_docker_client, create_ssh_client
6
- from .network_agents import DataProcessingWorker, DeployType
4
+ from docker import APIClient
5
+ from paramiko import SSHClient
6
+
7
+ from ..constants import RESOURCE_MANAGER_SERVER_PORT
8
+ from .connection_clients import CustomDockerClient, create_docker_client, create_ssh_client
9
+ from .network_agents import (
10
+ DataProcessingWorker, DeployType, deploy_agent_native_get_pid_hack)
7
11
 
8
12
 
9
13
  class DataHost:
@@ -11,6 +15,8 @@ class DataHost:
11
15
  self, host: str, username: str, password: str, keypath: str, workers: List[Dict], servers: List[Dict]
12
16
  ) -> None:
13
17
  self.host = host
18
+ self.resource_manager_port = RESOURCE_MANAGER_SERVER_PORT
19
+ self.resource_manager_pid = None
14
20
  self.username = username
15
21
  self.password = password
16
22
  self.keypath = keypath
@@ -22,14 +28,11 @@ class DataHost:
22
28
 
23
29
  # Connection clients, ssh for native deployment, docker for docker deployment
24
30
  self.ssh_client = None
25
- self.docker_client = None
26
-
27
- # Time to wait between deploying single workers
28
- self.wait_between_deploys: float = 0.3
31
+ self.docker_client: Optional[CustomDockerClient] = None
29
32
 
30
- # Lists of Processing Workers based on their deployment type
31
- self.workers_native = []
32
- self.workers_docker = []
33
+ # Lists of network agents based on their agent and deployment type
34
+ self.workers_native: List[DataProcessingWorker] = []
35
+ self.workers_docker: List[DataProcessingWorker] = []
33
36
 
34
37
  if not workers:
35
38
  workers = []
@@ -68,6 +71,13 @@ class DataHost:
68
71
  self.docker_client = create_docker_client(self.host, self.username, self.password, self.keypath)
69
72
  return self.docker_client
70
73
 
74
+ def __deploy_network_agent_resource_manager_server(self, logger: Logger):
75
+ logger.info(f"Deploying resource manager server on host: {self.host}:{self.resource_manager_port}")
76
+ start_cmd = f"ocrd network resmgr-server --address {self.host}:{self.resource_manager_port} &"
77
+ pid = deploy_agent_native_get_pid_hack(logger, self.ssh_client, start_cmd)
78
+ logger.info(f"Deployed: OCR-D Resource Manager Server [{pid}]: {self.host}:{self.resource_manager_port}")
79
+ self.resource_manager_pid = pid
80
+
71
81
  def __deploy_single_worker(
72
82
  self, logger: Logger, worker_data: DataProcessingWorker,
73
83
  mongodb_url: str, rabbitmq_url: str
@@ -86,7 +96,6 @@ class DataHost:
86
96
  connection_client = self.docker_client
87
97
 
88
98
  worker_data.deploy_network_agent(logger, connection_client, mongodb_url, rabbitmq_url)
89
- sleep(self.wait_between_deploys)
90
99
 
91
100
  def __deploy_all_workers(self, logger: Logger, mongodb_url: str, rabbitmq_url: str):
92
101
  logger.info(f"Deploying processing workers on host: {self.host}")
@@ -95,17 +104,24 @@ class DataHost:
95
104
  logger.info("No processing workers found to be deployed")
96
105
  for data_worker in self.workers_native:
97
106
  self.__deploy_single_worker(logger, data_worker, mongodb_url, rabbitmq_url)
107
+ logger.info(f"Deployed: {data_worker}")
98
108
  for data_worker in self.workers_docker:
99
109
  self.__deploy_single_worker(logger, data_worker, mongodb_url, rabbitmq_url)
110
+ logger.info(f"Deployed: {data_worker}")
100
111
 
101
112
  def deploy_workers(self, logger: Logger, mongodb_url: str, rabbitmq_url: str) -> None:
102
113
  if self.needs_ssh_connector and not self.ssh_client:
103
114
  logger.debug("Creating missing ssh connector before deploying")
104
- self.ssh_client = self.create_connection_client(client_type="ssh")
115
+ client = self.create_connection_client(client_type="ssh")
116
+ assert isinstance(client, SSHClient)
117
+ self.ssh_client = client
105
118
  if self.needs_docker_connector:
106
119
  logger.debug("Creating missing docker connector before deploying")
107
- self.docker_client = self.create_connection_client(client_type="docker")
120
+ client = self.create_connection_client(client_type="docker")
121
+ assert isinstance(client, CustomDockerClient)
122
+ self.docker_client = client
108
123
 
124
+ self.__deploy_network_agent_resource_manager_server(logger)
109
125
  self.__deploy_all_workers(logger=logger, mongodb_url=mongodb_url, rabbitmq_url=rabbitmq_url)
110
126
 
111
127
  if self.ssh_client:
@@ -115,6 +131,12 @@ class DataHost:
115
131
  self.docker_client.close()
116
132
  self.docker_client = None
117
133
 
134
+ def __stop_network_agent_resource_manager_server(self, logger: Logger):
135
+ logger.info(f"Stopping OCR-D Resource Manager Server [{self.resource_manager_pid}]: "
136
+ f"{self.host}:{self.resource_manager_port}")
137
+ assert self.ssh_client, "SSH client connection missing"
138
+ self.ssh_client.exec_command(f"kill {self.resource_manager_pid}")
139
+
118
140
  def __stop_worker(self, logger: Logger, name: str, deploy_type: DeployType, pid: str):
119
141
  worker_info = f"Processing Worker: deploy: {deploy_type}, name: {name}"
120
142
  if not pid:
@@ -132,10 +154,15 @@ class DataHost:
132
154
  def stop_workers(self, logger: Logger):
133
155
  if self.needs_ssh_connector and not self.ssh_client:
134
156
  logger.debug("Creating missing ssh connector before stopping")
135
- self.ssh_client = self.create_connection_client(client_type="ssh")
157
+ client = self.create_connection_client(client_type="ssh")
158
+ assert isinstance(client, SSHClient)
159
+ self.ssh_client = client
136
160
  if self.needs_docker_connector and not self.docker_client:
137
161
  logger.debug("Creating missing docker connector before stopping")
138
- self.docker_client = self.create_connection_client(client_type="docker")
162
+ client = self.create_connection_client(client_type="docker")
163
+ assert isinstance(client, CustomDockerClient)
164
+ self.docker_client = client
165
+ self.__stop_network_agent_resource_manager_server(logger=logger)
139
166
 
140
167
  logger.info(f"Stopping processing workers on host: {self.host}")
141
168
  amount_workers = len(self.workers_native) + len(self.workers_docker)
@@ -1,4 +1,5 @@
1
1
  from logging import Logger
2
+ from time import sleep
2
3
  from typing import Any
3
4
 
4
5
  from re import search as re_search
@@ -25,7 +26,8 @@ def deploy_agent_native_get_pid_hack(logger: Logger, ssh_client, start_cmd: str)
25
26
  output = stdout.read().decode("utf-8")
26
27
  stdout.close()
27
28
  stdin.close()
28
- return re_search(r"xyz([0-9]+)xyz", output).group(1) # type: ignore
29
+ pid = re_search(r"xyz([0-9]+)xyz", output).group(1) # type: ignore
30
+ return pid
29
31
 
30
32
 
31
33
  # TODO: Implement the actual method that is missing
@@ -51,6 +53,12 @@ class DataNetworkAgent:
51
53
  # The id is assigned when the agent is deployed
52
54
  self.pid = pid
53
55
 
56
+ # Time to wait between deploying agents
57
+ self.wait_between_agent_deploys: float = 0.3
58
+
59
+ def __str__(self):
60
+ return f"{self.pid} {self.deploy_type} {self.processor_name} on host: {self.host}"
61
+
54
62
  def _start_native_instance(self, logger: Logger, ssh_client, start_cmd: str):
55
63
  if self.deploy_type != DeployType.NATIVE:
56
64
  raise RuntimeError(f"Mismatch of deploy type when starting network agent: {self.processor_name}")
@@ -76,11 +84,17 @@ class DataProcessingWorker(DataNetworkAgent):
76
84
  def deploy_network_agent(self, logger: Logger, connector_client, database_url: str, queue_url: str):
77
85
  if self.deploy_type == DeployType.NATIVE:
78
86
  start_cmd = f"{self.processor_name} --database {database_url} --queue {queue_url} &"
87
+ assert connector_client, f"SSH client connection missing."
79
88
  self.pid = self._start_native_instance(logger, connector_client, start_cmd)
89
+ sleep(self.wait_between_agent_deploys)
80
90
  return self.pid
81
91
  if self.deploy_type == DeployType.DOCKER:
82
92
  # TODO: add real command to start processing worker in docker here
83
93
  start_cmd = ""
94
+ assert connector_client, f"Docker client connection missing."
95
+ if not start_cmd:
96
+ raise RuntimeError("Missing start command for the Processing Worker in docker mode")
84
97
  self.pid = self._start_docker_instance(logger, connector_client, start_cmd)
98
+ sleep(self.wait_between_agent_deploys)
85
99
  return self.pid
86
100
  raise RuntimeError(f"Unknown deploy type of {self.__dict__}")
ocrd_utils/__init__.py CHANGED
@@ -70,7 +70,8 @@ Utility functions and constants usable in various circumstances.
70
70
 
71
71
  filesystem-related utilities
72
72
 
73
- * :py:func:`is_string`,
73
+ * :py:func:`is_git_url`,
74
+ :py:func:`is_string`,
74
75
  :py:func:`membername`,
75
76
  :py:func:`concat_padded`,
76
77
  :py:func:`nth_url_segment`,
@@ -118,6 +119,7 @@ from .constants import (
118
119
  REGEX_PREFIX,
119
120
  REGEX_FILE_ID,
120
121
  RESOURCE_LOCATIONS,
122
+ RESOURCE_TYPES,
121
123
  LOG_FORMAT,
122
124
  LOG_TIMEFMT,
123
125
  VERSION,
@@ -184,9 +186,11 @@ from .os import (
184
186
  get_processor_resource_types,
185
187
  get_ocrd_tool_json,
186
188
  get_moduledir,
189
+ get_env_locations,
187
190
  guess_media_type,
188
191
  list_all_resources,
189
192
  is_file_in_directory,
193
+ is_git_url,
190
194
  list_resource_candidates,
191
195
  atomic_write,
192
196
  pushd_popd,