temporal-normalization-spacy 2.2.2__tar.gz → 2.3.0__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.
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/MANIFEST.in +1 -1
- {temporal_normalization_spacy-2.2.2/temporal_normalization_spacy.egg-info → temporal_normalization_spacy-2.3.0}/PKG-INFO +16 -15
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/README.md +13 -14
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/setup.py +3 -2
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/index.py +9 -1
- temporal_normalization_spacy-2.2.2/temporal_normalization/libs/temporal-normalization-2.1.0.jar → temporal_normalization_spacy-2.3.0/temporal_normalization/libs/temporal-normalization-2.2.0.jar +0 -0
- temporal_normalization_spacy-2.3.0/temporal_normalization/process/java_process.py +274 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0/temporal_normalization_spacy.egg-info}/PKG-INFO +16 -15
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization_spacy.egg-info/SOURCES.txt +1 -1
- temporal_normalization_spacy-2.2.2/temporal_normalization/process/java_process.py +0 -176
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/LICENSE +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/setup.cfg +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/__init__.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/commons/__init__.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/commons/print_utils.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/commons/temporal_models.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/commons/temporal_types.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/factory.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization/process/__init__.py +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization_spacy.egg-info/dependency_links.txt +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization_spacy.egg-info/entry_points.txt +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization_spacy.egg-info/requires.txt +0 -0
- {temporal_normalization_spacy-2.2.2 → temporal_normalization_spacy-2.3.0}/temporal_normalization_spacy.egg-info/top_level.txt +0 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: temporal_normalization_spacy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A spaCy plugin for temporal normalization and extraction of historical dates in Romanian narrative texts.
|
|
5
5
|
Home-page: https://github.com/iliedorobat/timespan-normalization-spacy
|
|
6
6
|
Author: Ilie Cristian Dorobat
|
|
7
|
+
Author-email: ic.dorobat@gmail.com
|
|
7
8
|
Keywords: spacy,nlp,temporal normalization,timex,historical dates,romanian
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -19,6 +20,7 @@ Requires-Dist: spacy<4.0.0,>=3.8.7
|
|
|
19
20
|
Requires-Dist: py4j>=0.10.9.9
|
|
20
21
|
Requires-Dist: langdetect>=1.0.9
|
|
21
22
|
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
22
24
|
Dynamic: classifier
|
|
23
25
|
Dynamic: description
|
|
24
26
|
Dynamic: description-content-type
|
|
@@ -78,12 +80,11 @@ quality and reliability of date-related information extracted from text.
|
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
## Getting started
|
|
81
|
-
To integrate TeNs into spaCy pipelines you need the following:
|
|
82
83
|
|
|
83
84
|
### Prerequisites
|
|
84
85
|
- Python 3.9-3.12
|
|
85
86
|
- JRE 11-17
|
|
86
|
-
- spaCy 3.
|
|
87
|
+
- spaCy >=3.8.7,<4.0.0
|
|
87
88
|
- py4j 0.10.9.9
|
|
88
89
|
- langdetect 1.0.9
|
|
89
90
|
|
|
@@ -233,24 +234,24 @@ Start Edge:
|
|
|
233
234
|
Matched value: Sec al II-lea a.ch.
|
|
234
235
|
Matched Type: century
|
|
235
236
|
Normalized label: 2nd century BC
|
|
236
|
-
DBpedia uri: https://dbpedia.org/
|
|
237
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
237
238
|
|
|
238
239
|
End Edge:
|
|
239
240
|
Matched value: Sec al II-lea a.ch.
|
|
240
241
|
Matched Type: century
|
|
241
242
|
Normalized label: 2nd century BC
|
|
242
|
-
DBpedia uri: https://dbpedia.org/
|
|
243
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
243
244
|
|
|
244
245
|
Periods:
|
|
245
246
|
Matched value: Sec al II-lea a.ch.
|
|
246
247
|
Matched Type: century
|
|
247
248
|
Normalized label: 1st millennium BC
|
|
248
|
-
DBpedia uri: https://dbpedia.org/
|
|
249
|
+
DBpedia uri: https://dbpedia.org/resource/1st_millennium_BC
|
|
249
250
|
|
|
250
251
|
Matched value: Sec al II-lea a.ch.
|
|
251
252
|
Matched Type: century
|
|
252
253
|
Normalized label: 2nd century BC
|
|
253
|
-
DBpedia uri: https://dbpedia.org/
|
|
254
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
254
255
|
```
|
|
255
256
|
|
|
256
257
|
### Second Sentence
|
|
@@ -259,24 +260,24 @@ Start Edge:
|
|
|
259
260
|
Matched value: secolul XX
|
|
260
261
|
Matched Type: century
|
|
261
262
|
Normalized label: 20th century
|
|
262
|
-
DBpedia uri: https://dbpedia.org/
|
|
263
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
263
264
|
|
|
264
265
|
End Edge:
|
|
265
266
|
Matched value: secolul XX
|
|
266
267
|
Matched Type: century
|
|
267
268
|
Normalized label: 20th century
|
|
268
|
-
DBpedia uri: https://dbpedia.org/
|
|
269
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
269
270
|
|
|
270
271
|
Periods:
|
|
271
272
|
Matched value: secolul XX
|
|
272
273
|
Matched Type: century
|
|
273
274
|
Normalized label: 2nd millennium
|
|
274
|
-
DBpedia uri: https://dbpedia.org/
|
|
275
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_millennium
|
|
275
276
|
|
|
276
277
|
Matched value: secolul XX
|
|
277
278
|
Matched Type: century
|
|
278
279
|
Normalized label: 20th century
|
|
279
|
-
DBpedia uri: https://dbpedia.org/
|
|
280
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
280
281
|
```
|
|
281
282
|
|
|
282
283
|
### Third Sentence
|
|
@@ -285,24 +286,24 @@ Start Edge:
|
|
|
285
286
|
Matched value: Sec. 21
|
|
286
287
|
Matched Type: century
|
|
287
288
|
Normalized label: 21st century
|
|
288
|
-
DBpedia uri: https://dbpedia.org/
|
|
289
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
289
290
|
|
|
290
291
|
End Edge:
|
|
291
292
|
Matched value: Sec. 21
|
|
292
293
|
Matched Type: century
|
|
293
294
|
Normalized label: 21st century
|
|
294
|
-
DBpedia uri: https://dbpedia.org/
|
|
295
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
295
296
|
|
|
296
297
|
Periods:
|
|
297
298
|
Matched value: Sec. 21
|
|
298
299
|
Matched Type: century
|
|
299
300
|
Normalized label: 3rd millennium
|
|
300
|
-
DBpedia uri: https://dbpedia.org/
|
|
301
|
+
DBpedia uri: https://dbpedia.org/resource/3rd_millennium
|
|
301
302
|
|
|
302
303
|
Matched value: Sec. 21
|
|
303
304
|
Matched Type: century
|
|
304
305
|
Normalized label: 21st century
|
|
305
|
-
DBpedia uri: https://dbpedia.org/
|
|
306
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
306
307
|
```
|
|
307
308
|
|
|
308
309
|
|
|
@@ -47,12 +47,11 @@ quality and reliability of date-related information extracted from text.
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
## Getting started
|
|
50
|
-
To integrate TeNs into spaCy pipelines you need the following:
|
|
51
50
|
|
|
52
51
|
### Prerequisites
|
|
53
52
|
- Python 3.9-3.12
|
|
54
53
|
- JRE 11-17
|
|
55
|
-
- spaCy 3.
|
|
54
|
+
- spaCy >=3.8.7,<4.0.0
|
|
56
55
|
- py4j 0.10.9.9
|
|
57
56
|
- langdetect 1.0.9
|
|
58
57
|
|
|
@@ -202,24 +201,24 @@ Start Edge:
|
|
|
202
201
|
Matched value: Sec al II-lea a.ch.
|
|
203
202
|
Matched Type: century
|
|
204
203
|
Normalized label: 2nd century BC
|
|
205
|
-
DBpedia uri: https://dbpedia.org/
|
|
204
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
206
205
|
|
|
207
206
|
End Edge:
|
|
208
207
|
Matched value: Sec al II-lea a.ch.
|
|
209
208
|
Matched Type: century
|
|
210
209
|
Normalized label: 2nd century BC
|
|
211
|
-
DBpedia uri: https://dbpedia.org/
|
|
210
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
212
211
|
|
|
213
212
|
Periods:
|
|
214
213
|
Matched value: Sec al II-lea a.ch.
|
|
215
214
|
Matched Type: century
|
|
216
215
|
Normalized label: 1st millennium BC
|
|
217
|
-
DBpedia uri: https://dbpedia.org/
|
|
216
|
+
DBpedia uri: https://dbpedia.org/resource/1st_millennium_BC
|
|
218
217
|
|
|
219
218
|
Matched value: Sec al II-lea a.ch.
|
|
220
219
|
Matched Type: century
|
|
221
220
|
Normalized label: 2nd century BC
|
|
222
|
-
DBpedia uri: https://dbpedia.org/
|
|
221
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
223
222
|
```
|
|
224
223
|
|
|
225
224
|
### Second Sentence
|
|
@@ -228,24 +227,24 @@ Start Edge:
|
|
|
228
227
|
Matched value: secolul XX
|
|
229
228
|
Matched Type: century
|
|
230
229
|
Normalized label: 20th century
|
|
231
|
-
DBpedia uri: https://dbpedia.org/
|
|
230
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
232
231
|
|
|
233
232
|
End Edge:
|
|
234
233
|
Matched value: secolul XX
|
|
235
234
|
Matched Type: century
|
|
236
235
|
Normalized label: 20th century
|
|
237
|
-
DBpedia uri: https://dbpedia.org/
|
|
236
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
238
237
|
|
|
239
238
|
Periods:
|
|
240
239
|
Matched value: secolul XX
|
|
241
240
|
Matched Type: century
|
|
242
241
|
Normalized label: 2nd millennium
|
|
243
|
-
DBpedia uri: https://dbpedia.org/
|
|
242
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_millennium
|
|
244
243
|
|
|
245
244
|
Matched value: secolul XX
|
|
246
245
|
Matched Type: century
|
|
247
246
|
Normalized label: 20th century
|
|
248
|
-
DBpedia uri: https://dbpedia.org/
|
|
247
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
249
248
|
```
|
|
250
249
|
|
|
251
250
|
### Third Sentence
|
|
@@ -254,24 +253,24 @@ Start Edge:
|
|
|
254
253
|
Matched value: Sec. 21
|
|
255
254
|
Matched Type: century
|
|
256
255
|
Normalized label: 21st century
|
|
257
|
-
DBpedia uri: https://dbpedia.org/
|
|
256
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
258
257
|
|
|
259
258
|
End Edge:
|
|
260
259
|
Matched value: Sec. 21
|
|
261
260
|
Matched Type: century
|
|
262
261
|
Normalized label: 21st century
|
|
263
|
-
DBpedia uri: https://dbpedia.org/
|
|
262
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
264
263
|
|
|
265
264
|
Periods:
|
|
266
265
|
Matched value: Sec. 21
|
|
267
266
|
Matched Type: century
|
|
268
267
|
Normalized label: 3rd millennium
|
|
269
|
-
DBpedia uri: https://dbpedia.org/
|
|
268
|
+
DBpedia uri: https://dbpedia.org/resource/3rd_millennium
|
|
270
269
|
|
|
271
270
|
Matched value: Sec. 21
|
|
272
271
|
Matched Type: century
|
|
273
272
|
Normalized label: 21st century
|
|
274
|
-
DBpedia uri: https://dbpedia.org/
|
|
273
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
275
274
|
```
|
|
276
275
|
|
|
277
276
|
|
|
@@ -2,8 +2,9 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="temporal_normalization_spacy",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.3.0",
|
|
6
6
|
author="Ilie Cristian Dorobat",
|
|
7
|
+
author_email="ic.dorobat@gmail.com",
|
|
7
8
|
description="A spaCy plugin for temporal normalization and extraction of "
|
|
8
9
|
"historical dates in Romanian narrative texts.",
|
|
9
10
|
keywords="spacy, nlp, temporal normalization, timex, historical dates, romanian",
|
|
@@ -18,7 +19,7 @@ setup(
|
|
|
18
19
|
],
|
|
19
20
|
},
|
|
20
21
|
package_data={
|
|
21
|
-
"temporal_normalization.libs": ["temporal-normalization-2.
|
|
22
|
+
"temporal_normalization.libs": ["temporal-normalization-2.2.0.jar"],
|
|
22
23
|
},
|
|
23
24
|
install_requires=[
|
|
24
25
|
"spacy>=3.8.7,<4.0.0",
|
|
@@ -43,6 +43,11 @@ class TemporalNormalization:
|
|
|
43
43
|
self.nlp = nlp
|
|
44
44
|
self.count = 0
|
|
45
45
|
|
|
46
|
+
# Initialize connection attributes before starting the Java gateway, as initialization may fail
|
|
47
|
+
# (e.g., during Flask's debug reloader), and __del__() may still be called on a partial instance.
|
|
48
|
+
self.java_process = None
|
|
49
|
+
self.gateway = None
|
|
50
|
+
|
|
46
51
|
root_path = str(Path(__file__).resolve().parent.parent)
|
|
47
52
|
java_process, gateway = start_conn(root_path)
|
|
48
53
|
self.java_process: subprocess.Popen = java_process
|
|
@@ -97,7 +102,10 @@ class TemporalNormalization:
|
|
|
97
102
|
network sockets that might otherwise persist after the Python process ends.
|
|
98
103
|
"""
|
|
99
104
|
|
|
100
|
-
|
|
105
|
+
if self.java_process is not None or self.gateway is not None:
|
|
106
|
+
close_conn(self.java_process, self.gateway)
|
|
107
|
+
else:
|
|
108
|
+
print(f"⚠️ No Java process or Python connection to close.")
|
|
101
109
|
|
|
102
110
|
|
|
103
111
|
def _prepare_str_patterns(expressions: list[TemporalExpression]) -> list[str]:
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import re
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from py4j.java_gateway import JavaGateway, GatewayParameters
|
|
9
|
+
from py4j.protocol import Py4JNetworkError
|
|
10
|
+
|
|
11
|
+
from temporal_normalization.commons.print_utils import console
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def start_conn(root_path: str) -> tuple[subprocess.Popen, JavaGateway]:
|
|
15
|
+
"""
|
|
16
|
+
Starts the Java temporal normalization process and establishes a Py4J gateway connection.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
root_path (str): The root directory of the project.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
tuple[subprocess.Popen, JavaGateway]:
|
|
23
|
+
- The subprocess.Popen object representing the running Java process.
|
|
24
|
+
- The JavaGateway object representing the active Py4J connection.
|
|
25
|
+
|
|
26
|
+
Note:
|
|
27
|
+
- Requires Java 11 or higher to be installed and accessible in the system PATH.
|
|
28
|
+
- Requires `temporal-normalization-2.2.0.jar` to be present in the `libs` directory.
|
|
29
|
+
- The caller is responsible for closing the gateway and terminating the Java process
|
|
30
|
+
after usage to avoid orphaned processes.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
check_java_version()
|
|
34
|
+
|
|
35
|
+
gateway_started = threading.Event()
|
|
36
|
+
gateway_error = threading.Event()
|
|
37
|
+
|
|
38
|
+
jar_path = (
|
|
39
|
+
f"{root_path}/temporal_normalization/libs/temporal-normalization-2.2.0.jar"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def stdout_callback(line: str):
|
|
43
|
+
if "Gateway Server Started" in line:
|
|
44
|
+
gateway_started.set()
|
|
45
|
+
print(line.strip())
|
|
46
|
+
|
|
47
|
+
stderr_lines = []
|
|
48
|
+
def stderr_callback(line: str):
|
|
49
|
+
stderr_lines.append(line)
|
|
50
|
+
|
|
51
|
+
if "Failed to bind" in line or "Address already in use" in line:
|
|
52
|
+
gateway_error.set()
|
|
53
|
+
|
|
54
|
+
java_process = subprocess.Popen(
|
|
55
|
+
["java", "-jar", jar_path, "--python"],
|
|
56
|
+
stdout=subprocess.PIPE,
|
|
57
|
+
stderr=subprocess.PIPE,
|
|
58
|
+
text=True,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
threading.Thread(target=drain_stream, args=(java_process.stdout, stdout_callback), daemon=True).start()
|
|
62
|
+
threading.Thread(target=drain_stream, args=(java_process.stderr, stderr_callback), daemon=True).start()
|
|
63
|
+
|
|
64
|
+
timeout = 10.0
|
|
65
|
+
start_time = time.monotonic()
|
|
66
|
+
|
|
67
|
+
while not gateway_started.is_set():
|
|
68
|
+
if gateway_error.wait(timeout=0.1):
|
|
69
|
+
error = "\n\t".join(stderr_lines)
|
|
70
|
+
|
|
71
|
+
if java_process.poll() is None:
|
|
72
|
+
java_process.terminate()
|
|
73
|
+
|
|
74
|
+
raise RuntimeError(
|
|
75
|
+
"Java Gateway failed to start.\n"
|
|
76
|
+
f"Java stderr:\n\t{error}"
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if time.monotonic() - start_time >= timeout:
|
|
80
|
+
error = "\n\t".join(stderr_lines)
|
|
81
|
+
|
|
82
|
+
java_process.terminate()
|
|
83
|
+
java_process.wait()
|
|
84
|
+
|
|
85
|
+
raise RuntimeError(
|
|
86
|
+
f"Java Gateway did not start within 10 seconds.\n"
|
|
87
|
+
f"Java stderr:\n\t{error}"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
time.sleep(0.1)
|
|
91
|
+
|
|
92
|
+
gateway = JavaGateway(
|
|
93
|
+
gateway_parameters=GatewayParameters(auto_convert=True, read_timeout=None),
|
|
94
|
+
callback_server_parameters=None,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
print("Python connection established.")
|
|
98
|
+
|
|
99
|
+
return java_process, gateway
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def close_conn(java_process: subprocess.Popen | None, gateway: JavaGateway | None) -> None:
|
|
103
|
+
"""
|
|
104
|
+
Closes the Py4J gateway connection and terminates the associated Java process.
|
|
105
|
+
|
|
106
|
+
This function performs the shutdown sequence by:
|
|
107
|
+
1. Attempting to gracefully close the Py4J gateway connection.
|
|
108
|
+
- If the Java process has already terminated, the resulting
|
|
109
|
+
Py4JNetworkError is handled without raising an exception.
|
|
110
|
+
2. Terminating the Java process if it is still running.
|
|
111
|
+
3. Reporting the shutdown status for each resource.
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
java_process (subprocess.Popen | None): The Java process launched
|
|
115
|
+
with subprocess, or None if the process was not successfully
|
|
116
|
+
initialized.
|
|
117
|
+
gateway (JavaGateway | None): The active Py4J gateway connection,
|
|
118
|
+
or None if the gateway was not successfully initialized.
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
- Call this function once all interactions with the Java process
|
|
122
|
+
have been completed.
|
|
123
|
+
- It is safe to call even if either the gateway or Java process
|
|
124
|
+
has already been closed or was not successfully initialized.
|
|
125
|
+
- The Py4J gateway is closed before the Java process is terminated.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
_close_gateway_conn(gateway)
|
|
129
|
+
_close_java_conn(java_process)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _close_gateway_conn(gateway: JavaGateway | None) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Closes the active Py4J gateway connection, if one exists.
|
|
135
|
+
|
|
136
|
+
Attempts to gracefully shut down the connection between Python and
|
|
137
|
+
the Java process. If the Java process has already terminated, the
|
|
138
|
+
resulting Py4JNetworkError is handled and reported without raising
|
|
139
|
+
an exception.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
gateway (JavaGateway | None): The active Py4J gateway connection,
|
|
143
|
+
or None if the connection was not successfully initialized.
|
|
144
|
+
|
|
145
|
+
Notes:
|
|
146
|
+
- It is safe to call this function when the gateway is None.
|
|
147
|
+
- It is safe to call this function after the Java process has
|
|
148
|
+
already terminated.
|
|
149
|
+
- Unexpected errors during gateway shutdown are caught and
|
|
150
|
+
reported rather than propagated.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
if gateway is not None:
|
|
154
|
+
try:
|
|
155
|
+
# Proper way to shut down Py4J
|
|
156
|
+
gateway.shutdown()
|
|
157
|
+
print("✅ Python connection closed.")
|
|
158
|
+
except Py4JNetworkError:
|
|
159
|
+
print("⚠️ Java process already shut down.")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
print(f"⚠️ Error shutting down gateway: {e}")
|
|
162
|
+
else:
|
|
163
|
+
print(f"⚠️ No Python connection to close.")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _close_java_conn(java_process: subprocess.Popen | None) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Terminates the Java process, if it is still running.
|
|
169
|
+
|
|
170
|
+
Checks whether the Java process is active before attempting to
|
|
171
|
+
terminate it. If the process has already exited, no termination
|
|
172
|
+
is attempted and a status message is printed.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
java_process (subprocess.Popen | None): The Java process started
|
|
176
|
+
with subprocess, or None if the process was not successfully
|
|
177
|
+
initialized.
|
|
178
|
+
|
|
179
|
+
Notes:
|
|
180
|
+
- It is safe to call this function when java_process is None.
|
|
181
|
+
- It is safe to call this function after the Java process has
|
|
182
|
+
already terminated.
|
|
183
|
+
- If the process is still running, terminate() is called and the
|
|
184
|
+
function waits for the process to exit.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
if java_process is None:
|
|
188
|
+
print("⚠️ No Java process to close.")
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
if java_process.poll() is None:
|
|
192
|
+
# Terminate Java process
|
|
193
|
+
java_process.terminate()
|
|
194
|
+
java_process.wait()
|
|
195
|
+
print("✅ Java process terminated.")
|
|
196
|
+
else:
|
|
197
|
+
print("⚠️ Java process already terminated.")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def drain_stream(stream: io.TextIOBase, callback=None) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Consumes the output from a given stream until a specific marker is found,
|
|
203
|
+
then closes the stream.
|
|
204
|
+
|
|
205
|
+
This function is typically used to monitor the stdout or stderr of a subprocess
|
|
206
|
+
(e.g., a Java process started from Python) and detect when a certain event occurs,
|
|
207
|
+
such as the initialization of a gateway server. Once the marker line is encountered,
|
|
208
|
+
the function prints it (or logs it) and terminates the stream reading.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
stream (io.TextIOBase): A text-based stream object to read from, usually
|
|
212
|
+
subprocess.stdout or subprocess.stderr.
|
|
213
|
+
callback (callable, optional): A function that takes a single string argument (line).
|
|
214
|
+
It will be called for every line read from the stream.
|
|
215
|
+
Useful for detecting specific markers, logging, or
|
|
216
|
+
triggering events when certain output appears.
|
|
217
|
+
|
|
218
|
+
Raises:
|
|
219
|
+
AttributeError: If the provided `stream` does not have `readline` or `close` methods.
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
for line in iter(stream.readline, ""):
|
|
223
|
+
line = line.strip()
|
|
224
|
+
if callback:
|
|
225
|
+
callback(line)
|
|
226
|
+
|
|
227
|
+
stream.close()
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def check_java_version() -> None:
|
|
231
|
+
"""
|
|
232
|
+
Verifies that Java is installed and meets the minimum required version.
|
|
233
|
+
|
|
234
|
+
This function checks for the presence of the Java executable in the system PATH,
|
|
235
|
+
runs ``java -version``, and ensures that the version is at least 11. If Java is not
|
|
236
|
+
installed or the version is too low, it logs an error using ``console.error``.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
Logs error messages, but does not raise exceptions directly.
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
min_version = 11
|
|
243
|
+
java_path = shutil.which("java")
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
if java_path:
|
|
247
|
+
# Run the command to check the Java version
|
|
248
|
+
result = subprocess.run(
|
|
249
|
+
[java_path, "-version"], capture_output=True, text=True
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Print the version information (Java version is printed to stderr)
|
|
253
|
+
if result.returncode == 0:
|
|
254
|
+
version_output = result.stderr
|
|
255
|
+
match = re.search(r'version "(\d+\.\d+)', version_output)
|
|
256
|
+
|
|
257
|
+
if match:
|
|
258
|
+
crr_version = float(match.group(1))
|
|
259
|
+
if crr_version < min_version:
|
|
260
|
+
console.error(
|
|
261
|
+
f"Java {crr_version} is installed, but version {min_version} is required." # noqa 501
|
|
262
|
+
)
|
|
263
|
+
else:
|
|
264
|
+
console.error("Could not extract Java version.")
|
|
265
|
+
else:
|
|
266
|
+
console.error("Error occurred while checking the version.")
|
|
267
|
+
else:
|
|
268
|
+
console.error("Java not found.")
|
|
269
|
+
except Exception as e:
|
|
270
|
+
console.error(e.__str__())
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
if __name__ == "__main__":
|
|
274
|
+
pass
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: temporal_normalization_spacy
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A spaCy plugin for temporal normalization and extraction of historical dates in Romanian narrative texts.
|
|
5
5
|
Home-page: https://github.com/iliedorobat/timespan-normalization-spacy
|
|
6
6
|
Author: Ilie Cristian Dorobat
|
|
7
|
+
Author-email: ic.dorobat@gmail.com
|
|
7
8
|
Keywords: spacy,nlp,temporal normalization,timex,historical dates,romanian
|
|
8
9
|
Classifier: Programming Language :: Python :: 3
|
|
9
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
@@ -19,6 +20,7 @@ Requires-Dist: spacy<4.0.0,>=3.8.7
|
|
|
19
20
|
Requires-Dist: py4j>=0.10.9.9
|
|
20
21
|
Requires-Dist: langdetect>=1.0.9
|
|
21
22
|
Dynamic: author
|
|
23
|
+
Dynamic: author-email
|
|
22
24
|
Dynamic: classifier
|
|
23
25
|
Dynamic: description
|
|
24
26
|
Dynamic: description-content-type
|
|
@@ -78,12 +80,11 @@ quality and reliability of date-related information extracted from text.
|
|
|
78
80
|
|
|
79
81
|
|
|
80
82
|
## Getting started
|
|
81
|
-
To integrate TeNs into spaCy pipelines you need the following:
|
|
82
83
|
|
|
83
84
|
### Prerequisites
|
|
84
85
|
- Python 3.9-3.12
|
|
85
86
|
- JRE 11-17
|
|
86
|
-
- spaCy 3.
|
|
87
|
+
- spaCy >=3.8.7,<4.0.0
|
|
87
88
|
- py4j 0.10.9.9
|
|
88
89
|
- langdetect 1.0.9
|
|
89
90
|
|
|
@@ -233,24 +234,24 @@ Start Edge:
|
|
|
233
234
|
Matched value: Sec al II-lea a.ch.
|
|
234
235
|
Matched Type: century
|
|
235
236
|
Normalized label: 2nd century BC
|
|
236
|
-
DBpedia uri: https://dbpedia.org/
|
|
237
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
237
238
|
|
|
238
239
|
End Edge:
|
|
239
240
|
Matched value: Sec al II-lea a.ch.
|
|
240
241
|
Matched Type: century
|
|
241
242
|
Normalized label: 2nd century BC
|
|
242
|
-
DBpedia uri: https://dbpedia.org/
|
|
243
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
243
244
|
|
|
244
245
|
Periods:
|
|
245
246
|
Matched value: Sec al II-lea a.ch.
|
|
246
247
|
Matched Type: century
|
|
247
248
|
Normalized label: 1st millennium BC
|
|
248
|
-
DBpedia uri: https://dbpedia.org/
|
|
249
|
+
DBpedia uri: https://dbpedia.org/resource/1st_millennium_BC
|
|
249
250
|
|
|
250
251
|
Matched value: Sec al II-lea a.ch.
|
|
251
252
|
Matched Type: century
|
|
252
253
|
Normalized label: 2nd century BC
|
|
253
|
-
DBpedia uri: https://dbpedia.org/
|
|
254
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_century_BC
|
|
254
255
|
```
|
|
255
256
|
|
|
256
257
|
### Second Sentence
|
|
@@ -259,24 +260,24 @@ Start Edge:
|
|
|
259
260
|
Matched value: secolul XX
|
|
260
261
|
Matched Type: century
|
|
261
262
|
Normalized label: 20th century
|
|
262
|
-
DBpedia uri: https://dbpedia.org/
|
|
263
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
263
264
|
|
|
264
265
|
End Edge:
|
|
265
266
|
Matched value: secolul XX
|
|
266
267
|
Matched Type: century
|
|
267
268
|
Normalized label: 20th century
|
|
268
|
-
DBpedia uri: https://dbpedia.org/
|
|
269
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
269
270
|
|
|
270
271
|
Periods:
|
|
271
272
|
Matched value: secolul XX
|
|
272
273
|
Matched Type: century
|
|
273
274
|
Normalized label: 2nd millennium
|
|
274
|
-
DBpedia uri: https://dbpedia.org/
|
|
275
|
+
DBpedia uri: https://dbpedia.org/resource/2nd_millennium
|
|
275
276
|
|
|
276
277
|
Matched value: secolul XX
|
|
277
278
|
Matched Type: century
|
|
278
279
|
Normalized label: 20th century
|
|
279
|
-
DBpedia uri: https://dbpedia.org/
|
|
280
|
+
DBpedia uri: https://dbpedia.org/resource/20th_century
|
|
280
281
|
```
|
|
281
282
|
|
|
282
283
|
### Third Sentence
|
|
@@ -285,24 +286,24 @@ Start Edge:
|
|
|
285
286
|
Matched value: Sec. 21
|
|
286
287
|
Matched Type: century
|
|
287
288
|
Normalized label: 21st century
|
|
288
|
-
DBpedia uri: https://dbpedia.org/
|
|
289
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
289
290
|
|
|
290
291
|
End Edge:
|
|
291
292
|
Matched value: Sec. 21
|
|
292
293
|
Matched Type: century
|
|
293
294
|
Normalized label: 21st century
|
|
294
|
-
DBpedia uri: https://dbpedia.org/
|
|
295
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
295
296
|
|
|
296
297
|
Periods:
|
|
297
298
|
Matched value: Sec. 21
|
|
298
299
|
Matched Type: century
|
|
299
300
|
Normalized label: 3rd millennium
|
|
300
|
-
DBpedia uri: https://dbpedia.org/
|
|
301
|
+
DBpedia uri: https://dbpedia.org/resource/3rd_millennium
|
|
301
302
|
|
|
302
303
|
Matched value: Sec. 21
|
|
303
304
|
Matched Type: century
|
|
304
305
|
Normalized label: 21st century
|
|
305
|
-
DBpedia uri: https://dbpedia.org/
|
|
306
|
+
DBpedia uri: https://dbpedia.org/resource/21st_century
|
|
306
307
|
```
|
|
307
308
|
|
|
308
309
|
|
|
@@ -9,7 +9,7 @@ temporal_normalization/commons/__init__.py
|
|
|
9
9
|
temporal_normalization/commons/print_utils.py
|
|
10
10
|
temporal_normalization/commons/temporal_models.py
|
|
11
11
|
temporal_normalization/commons/temporal_types.py
|
|
12
|
-
temporal_normalization/libs/temporal-normalization-2.
|
|
12
|
+
temporal_normalization/libs/temporal-normalization-2.2.0.jar
|
|
13
13
|
temporal_normalization/process/__init__.py
|
|
14
14
|
temporal_normalization/process/java_process.py
|
|
15
15
|
temporal_normalization_spacy.egg-info/PKG-INFO
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import io
|
|
2
|
-
import re
|
|
3
|
-
import shutil
|
|
4
|
-
import subprocess
|
|
5
|
-
import threading
|
|
6
|
-
|
|
7
|
-
from py4j.java_gateway import JavaGateway, GatewayParameters, CallbackServerParameters
|
|
8
|
-
from py4j.protocol import Py4JNetworkError
|
|
9
|
-
|
|
10
|
-
from temporal_normalization.commons.print_utils import console
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
gateway_started = threading.Event()
|
|
14
|
-
|
|
15
|
-
def start_conn(root_path: str) -> tuple[subprocess.Popen, JavaGateway]:
|
|
16
|
-
"""
|
|
17
|
-
Starts the Java temporal normalization process and establishes a Py4J gateway connection.
|
|
18
|
-
|
|
19
|
-
Args:
|
|
20
|
-
root_path (str): The root directory of the project.
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
tuple[subprocess.Popen, JavaGateway]:
|
|
24
|
-
- The subprocess.Popen object representing the running Java process.
|
|
25
|
-
- The JavaGateway object representing the active Py4J connection.
|
|
26
|
-
|
|
27
|
-
Note:
|
|
28
|
-
- Requires Java 11 or higher to be installed and accessible in the system PATH.
|
|
29
|
-
- Requires `temporal-normalization-2.1.0.jar` to be present in the `libs` directory.
|
|
30
|
-
- The caller is responsible for closing the gateway and terminating the Java process
|
|
31
|
-
after usage to avoid orphaned processes.
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
check_java_version()
|
|
35
|
-
|
|
36
|
-
jar_path = (
|
|
37
|
-
f"{root_path}/temporal_normalization/libs/temporal-normalization-2.1.0.jar"
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
def stdout_callback(line: str):
|
|
41
|
-
if "Gateway Server Started" in line:
|
|
42
|
-
gateway_started.set()
|
|
43
|
-
print(line.strip())
|
|
44
|
-
|
|
45
|
-
java_process = subprocess.Popen(
|
|
46
|
-
["java", "-jar", jar_path, "--python"],
|
|
47
|
-
stdout=subprocess.PIPE,
|
|
48
|
-
stderr=subprocess.PIPE,
|
|
49
|
-
text=True,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
threading.Thread(target=drain_stream, args=(java_process.stdout, stdout_callback), daemon=True).start()
|
|
53
|
-
threading.Thread(target=drain_stream, args=(java_process.stderr,), daemon=True).start()
|
|
54
|
-
|
|
55
|
-
if not gateway_started.wait(timeout=10.0):
|
|
56
|
-
java_process.terminate()
|
|
57
|
-
raise RuntimeError("Java Gateway did not start within 10 seconds")
|
|
58
|
-
|
|
59
|
-
gateway = JavaGateway(
|
|
60
|
-
gateway_parameters=GatewayParameters(auto_convert=True, read_timeout=None),
|
|
61
|
-
callback_server_parameters=None,
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
print("Python connection established.")
|
|
65
|
-
|
|
66
|
-
return java_process, gateway
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def close_conn(java_process: subprocess.Popen, gateway: JavaGateway) -> None:
|
|
70
|
-
"""
|
|
71
|
-
Closes the active connection between Python and the Java process started via Py4J.
|
|
72
|
-
|
|
73
|
-
This function ensures a proper shutdown sequence:
|
|
74
|
-
1. Attempts to gracefully shut down the Py4J gateway connection.
|
|
75
|
-
- If the Java process is already closed, a Py4JNetworkError is caught and logged.
|
|
76
|
-
2. Terminates the underlying Java process.
|
|
77
|
-
3. Prints status messages for debugging/confirmation.
|
|
78
|
-
|
|
79
|
-
Args:
|
|
80
|
-
java_process (subprocess.Popen): The Java process launched with subprocess.
|
|
81
|
-
gateway (JavaGateway): The active Py4J gateway connection.
|
|
82
|
-
|
|
83
|
-
Notes:
|
|
84
|
-
- Call this function once you have finished all interactions with the Java process.
|
|
85
|
-
- It is safe to call even if the Java process has already exited.
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
# Proper way to shut down Py4J
|
|
90
|
-
gateway.shutdown()
|
|
91
|
-
print("✅ Python connection closed.")
|
|
92
|
-
except Py4JNetworkError:
|
|
93
|
-
print("⚠️ Java process already shut down.")
|
|
94
|
-
except Exception as e:
|
|
95
|
-
print(f"⚠️ Error shutting down gateway: {e}")
|
|
96
|
-
|
|
97
|
-
# Terminate Java process
|
|
98
|
-
java_process.terminate()
|
|
99
|
-
java_process.wait()
|
|
100
|
-
print("✅ Java process terminated.")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def drain_stream(stream: io.TextIOBase, callback=None) -> None:
|
|
104
|
-
"""
|
|
105
|
-
Consumes the output from a given stream until a specific marker is found,
|
|
106
|
-
then closes the stream.
|
|
107
|
-
|
|
108
|
-
This function is typically used to monitor the stdout or stderr of a subprocess
|
|
109
|
-
(e.g., a Java process started from Python) and detect when a certain event occurs,
|
|
110
|
-
such as the initialization of a gateway server. Once the marker line is encountered,
|
|
111
|
-
the function prints it (or logs it) and terminates the stream reading.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
stream (io.TextIOBase): A text-based stream object to read from, usually
|
|
115
|
-
subprocess.stdout or subprocess.stderr.
|
|
116
|
-
callback (callable, optional): A function that takes a single string argument (line).
|
|
117
|
-
It will be called for every line read from the stream.
|
|
118
|
-
Useful for detecting specific markers, logging, or
|
|
119
|
-
triggering events when certain output appears.
|
|
120
|
-
|
|
121
|
-
Raises:
|
|
122
|
-
AttributeError: If the provided `stream` does not have `readline` or `close` methods.
|
|
123
|
-
"""
|
|
124
|
-
for line in iter(stream.readline, ""):
|
|
125
|
-
line = line.strip()
|
|
126
|
-
if callback:
|
|
127
|
-
callback(line)
|
|
128
|
-
|
|
129
|
-
stream.close()
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def check_java_version() -> None:
|
|
133
|
-
"""
|
|
134
|
-
Verifies that Java is installed and meets the minimum required version.
|
|
135
|
-
|
|
136
|
-
This function checks for the presence of the Java executable in the system PATH,
|
|
137
|
-
runs ``java -version``, and ensures that the version is at least 11. If Java is not
|
|
138
|
-
installed or the version is too low, it logs an error using ``console.error``.
|
|
139
|
-
|
|
140
|
-
Raises:
|
|
141
|
-
Logs error messages, but does not raise exceptions directly.
|
|
142
|
-
"""
|
|
143
|
-
|
|
144
|
-
min_version = 11
|
|
145
|
-
java_path = shutil.which("java")
|
|
146
|
-
|
|
147
|
-
try:
|
|
148
|
-
if java_path:
|
|
149
|
-
# Run the command to check the Java version
|
|
150
|
-
result = subprocess.run(
|
|
151
|
-
[java_path, "-version"], capture_output=True, text=True
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
# Print the version information (Java version is printed to stderr)
|
|
155
|
-
if result.returncode == 0:
|
|
156
|
-
version_output = result.stderr
|
|
157
|
-
match = re.search(r'version "(\d+\.\d+)', version_output)
|
|
158
|
-
|
|
159
|
-
if match:
|
|
160
|
-
crr_version = float(match.group(1))
|
|
161
|
-
if crr_version < min_version:
|
|
162
|
-
console.error(
|
|
163
|
-
f"Java {crr_version} is installed, but version {min_version} is required." # noqa 501
|
|
164
|
-
)
|
|
165
|
-
else:
|
|
166
|
-
console.error("Could not extract Java version.")
|
|
167
|
-
else:
|
|
168
|
-
console.error("Error occurred while checking the version.")
|
|
169
|
-
else:
|
|
170
|
-
console.error("Java not found.")
|
|
171
|
-
except Exception as e:
|
|
172
|
-
console.error(e.__str__())
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if __name__ == "__main__":
|
|
176
|
-
pass
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|