kwic 0.1.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.
kwic-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eva
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
kwic-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,155 @@
1
+ Metadata-Version: 2.4
2
+ Name: kwic
3
+ Version: 0.1.0
4
+ Summary: Key Word In Context
5
+ Author: Eva
6
+ Author-email: Eva <eva.evangelisti03@gmail.com>
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Dist: spacy>=3.8.15
10
+ Requires-Dist: lemminflect>=0.2.3 ; extra == 'lemminflect'
11
+ Requires-Dist: stanza>=1.14.0 ; extra == 'stanza'
12
+ Requires-Python: >=3.12
13
+ Project-URL: Repository, https://github.com/evaevangelisti/kwic
14
+ Provides-Extra: lemminflect
15
+ Provides-Extra: stanza
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Key Word In Context
19
+
20
+ Locates the lemmas of a query in the context that attests them.
21
+
22
+ <!-- installation -->
23
+
24
+ ## Installation
25
+
26
+ ### Requirements
27
+
28
+ - Python 3.12 or later
29
+
30
+ ### From PyPI
31
+
32
+ ```sh
33
+ pip install kwic
34
+ ```
35
+
36
+ A search reads with spaCy, whose pipelines are published apart from the library:
37
+
38
+ ```sh
39
+ python -m spacy download en_core_web_sm
40
+ ```
41
+
42
+ The other engines are extras:
43
+
44
+ ```sh
45
+ pip install "kwic[stanza]"
46
+ pip install "kwic[lemminflect]"
47
+ ```
48
+
49
+ <!-- usage -->
50
+
51
+ ## Usage
52
+
53
+ A search takes one context and the lemmas to look for.
54
+
55
+ ```python
56
+ from kwic import Locator, POS, Query
57
+
58
+ locator = Locator()
59
+
60
+ locator.find("She found the keys she had lost.", [Query("find", POS.VERB)])
61
+ # (Match(lemma='find', pos=POS.VERB, form='found', word_index=1, offsets=(4, 9)),)
62
+ ```
63
+
64
+ ### Query
65
+
66
+ | Field | Default | |
67
+ | --- | --- | --- |
68
+ | `lemma` | | Dictionary form to look for, in whatever case |
69
+ | `pos` | `None` | Tag a one-word occurrence must carry; a longer lemma is not narrowed |
70
+ | `forms` | `()` | How else the lemma is written, taken where the engine read another |
71
+
72
+ The engine cuts the lemma into words, and a space and a hyphen are one to it.
73
+
74
+ ```python
75
+ locator.find("It was hunky-dory.", [Query("hunky dory")])
76
+ # (Match(lemma='hunky dory', pos=POS.ADJ, form='hunky-dory', word_index=2, offsets=(7, 17)),)
77
+ ```
78
+
79
+ A phrasal verb written apart runs from the verb to the particle.
80
+
81
+ ```python
82
+ locator.find("She gave the money up.", [Query("give up")])
83
+ # (Match(lemma='give up', pos=POS.VERB, form='gave the money up', word_index=1, offsets=(4, 21)),)
84
+ ```
85
+
86
+ ### Match
87
+
88
+ | Field | |
89
+ | --- | --- |
90
+ | `lemma` | The lemma you asked for, as you wrote it |
91
+ | `pos` | The tag it carries |
92
+ | `form` | How it is written |
93
+ | `word_index` | Where it opens among the words, from zero |
94
+ | `offsets` | Where it falls in the text, half-open and in code points |
95
+
96
+ ### Contexts
97
+
98
+ A context is a text, or the words it was split into. The second has no range.
99
+
100
+ ```python
101
+ locator.find(("She", "found", "the", "keys"), [Query("find")])
102
+ # (Match(lemma='find', pos=POS.VERB, form='found', word_index=1, offsets=None),)
103
+ ```
104
+
105
+ ### Many contexts
106
+
107
+ `find_all` takes context and lemmas in pairs, and reads a batch at a time.
108
+
109
+ ```python
110
+ for occurrences in locator.find_all(searches):
111
+ ...
112
+ ```
113
+
114
+ ### Engines
115
+
116
+ An engine reads the context; the search reads the engine.
117
+
118
+ ```python
119
+ from kwic.engines.stanza import StanzaEngine
120
+
121
+ locator = Locator(StanzaEngine())
122
+ ```
123
+
124
+ | Engine | Reads with | Install |
125
+ | --- | --- | --- |
126
+ | `SpacyEngine` | a spaCy pipeline, its English lemmatiser rules over the tag | `spacy` |
127
+ | `StanzaEngine` | Stanza, a dictionary with a neural model behind it | `kwic[stanza]` |
128
+ | `LemmInflectEngine` | spaCy for the tags, LemmInflect for the lemmas | `kwic[lemminflect]` |
129
+
130
+ An extra is imported from the module wrapping it, so a package without it still loads.
131
+
132
+ `SpacyEngine` takes the pipeline to load, `StanzaEngine` the language. Both parse unless told otherwise:
133
+
134
+ | Parser | Buys | Costs |
135
+ | --- | --- | --- |
136
+ | spaCy | phrasal verbs apart, and several universal tags | a tenth of a reading |
137
+ | Stanza | phrasal verbs apart | half its speed |
138
+
139
+ ```python
140
+ Locator(SpacyEngine(parse=False))
141
+ ```
142
+
143
+ <!-- accuracy -->
144
+
145
+ ## Accuracy
146
+
147
+ English-EWT test section: 2,077 sentences, 417 lemmas, 3,619 occurrences.
148
+
149
+ | Engine | Precision | Recall | F1 |
150
+ | --- | --- | --- | --- |
151
+ | `StanzaEngine` | 0.975 | 0.963 | 0.969 |
152
+ | `SpacyEngine`, `en_core_web_trf` | 0.981 | 0.905 | 0.942 |
153
+ | `SpacyEngine`, `en_core_web_lg` | 0.978 | 0.891 | 0.933 |
154
+ | `SpacyEngine`, `en_core_web_sm` | 0.978 | 0.882 | 0.928 |
155
+ | `LemmInflectEngine` | 0.976 | 0.872 | 0.921 |
kwic-0.1.0/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # Key Word In Context
2
+
3
+ Locates the lemmas of a query in the context that attests them.
4
+
5
+ <!-- installation -->
6
+
7
+ ## Installation
8
+
9
+ ### Requirements
10
+
11
+ - Python 3.12 or later
12
+
13
+ ### From PyPI
14
+
15
+ ```sh
16
+ pip install kwic
17
+ ```
18
+
19
+ A search reads with spaCy, whose pipelines are published apart from the library:
20
+
21
+ ```sh
22
+ python -m spacy download en_core_web_sm
23
+ ```
24
+
25
+ The other engines are extras:
26
+
27
+ ```sh
28
+ pip install "kwic[stanza]"
29
+ pip install "kwic[lemminflect]"
30
+ ```
31
+
32
+ <!-- usage -->
33
+
34
+ ## Usage
35
+
36
+ A search takes one context and the lemmas to look for.
37
+
38
+ ```python
39
+ from kwic import Locator, POS, Query
40
+
41
+ locator = Locator()
42
+
43
+ locator.find("She found the keys she had lost.", [Query("find", POS.VERB)])
44
+ # (Match(lemma='find', pos=POS.VERB, form='found', word_index=1, offsets=(4, 9)),)
45
+ ```
46
+
47
+ ### Query
48
+
49
+ | Field | Default | |
50
+ | --- | --- | --- |
51
+ | `lemma` | | Dictionary form to look for, in whatever case |
52
+ | `pos` | `None` | Tag a one-word occurrence must carry; a longer lemma is not narrowed |
53
+ | `forms` | `()` | How else the lemma is written, taken where the engine read another |
54
+
55
+ The engine cuts the lemma into words, and a space and a hyphen are one to it.
56
+
57
+ ```python
58
+ locator.find("It was hunky-dory.", [Query("hunky dory")])
59
+ # (Match(lemma='hunky dory', pos=POS.ADJ, form='hunky-dory', word_index=2, offsets=(7, 17)),)
60
+ ```
61
+
62
+ A phrasal verb written apart runs from the verb to the particle.
63
+
64
+ ```python
65
+ locator.find("She gave the money up.", [Query("give up")])
66
+ # (Match(lemma='give up', pos=POS.VERB, form='gave the money up', word_index=1, offsets=(4, 21)),)
67
+ ```
68
+
69
+ ### Match
70
+
71
+ | Field | |
72
+ | --- | --- |
73
+ | `lemma` | The lemma you asked for, as you wrote it |
74
+ | `pos` | The tag it carries |
75
+ | `form` | How it is written |
76
+ | `word_index` | Where it opens among the words, from zero |
77
+ | `offsets` | Where it falls in the text, half-open and in code points |
78
+
79
+ ### Contexts
80
+
81
+ A context is a text, or the words it was split into. The second has no range.
82
+
83
+ ```python
84
+ locator.find(("She", "found", "the", "keys"), [Query("find")])
85
+ # (Match(lemma='find', pos=POS.VERB, form='found', word_index=1, offsets=None),)
86
+ ```
87
+
88
+ ### Many contexts
89
+
90
+ `find_all` takes context and lemmas in pairs, and reads a batch at a time.
91
+
92
+ ```python
93
+ for occurrences in locator.find_all(searches):
94
+ ...
95
+ ```
96
+
97
+ ### Engines
98
+
99
+ An engine reads the context; the search reads the engine.
100
+
101
+ ```python
102
+ from kwic.engines.stanza import StanzaEngine
103
+
104
+ locator = Locator(StanzaEngine())
105
+ ```
106
+
107
+ | Engine | Reads with | Install |
108
+ | --- | --- | --- |
109
+ | `SpacyEngine` | a spaCy pipeline, its English lemmatiser rules over the tag | `spacy` |
110
+ | `StanzaEngine` | Stanza, a dictionary with a neural model behind it | `kwic[stanza]` |
111
+ | `LemmInflectEngine` | spaCy for the tags, LemmInflect for the lemmas | `kwic[lemminflect]` |
112
+
113
+ An extra is imported from the module wrapping it, so a package without it still loads.
114
+
115
+ `SpacyEngine` takes the pipeline to load, `StanzaEngine` the language. Both parse unless told otherwise:
116
+
117
+ | Parser | Buys | Costs |
118
+ | --- | --- | --- |
119
+ | spaCy | phrasal verbs apart, and several universal tags | a tenth of a reading |
120
+ | Stanza | phrasal verbs apart | half its speed |
121
+
122
+ ```python
123
+ Locator(SpacyEngine(parse=False))
124
+ ```
125
+
126
+ <!-- accuracy -->
127
+
128
+ ## Accuracy
129
+
130
+ English-EWT test section: 2,077 sentences, 417 lemmas, 3,619 occurrences.
131
+
132
+ | Engine | Precision | Recall | F1 |
133
+ | --- | --- | --- | --- |
134
+ | `StanzaEngine` | 0.975 | 0.963 | 0.969 |
135
+ | `SpacyEngine`, `en_core_web_trf` | 0.981 | 0.905 | 0.942 |
136
+ | `SpacyEngine`, `en_core_web_lg` | 0.978 | 0.891 | 0.933 |
137
+ | `SpacyEngine`, `en_core_web_sm` | 0.978 | 0.882 | 0.928 |
138
+ | `LemmInflectEngine` | 0.976 | 0.872 | 0.921 |
@@ -0,0 +1,137 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.3,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "kwic"
7
+ version = "0.1.0"
8
+ description = "Key Word In Context"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ dependencies = ["spacy>=3.8.15"]
14
+
15
+ [[project.authors]]
16
+ name = "Eva"
17
+ email = "eva.evangelisti03@gmail.com"
18
+
19
+ [project.optional-dependencies]
20
+ lemminflect = ["lemminflect>=0.2.3"]
21
+ stanza = ["stanza>=1.14.0"]
22
+
23
+ [project.urls]
24
+ Repository = "https://github.com/evaevangelisti/kwic"
25
+
26
+ [dependency-groups]
27
+ docs = [
28
+ "myst-parser>=5.1.0",
29
+ "shibuya>=2026.7.12",
30
+ "sphinx>=9.1.0",
31
+ ]
32
+ lint = ["ruff>=0.16.2"]
33
+ models = [
34
+ "en-core-web-lg",
35
+ "en-core-web-sm",
36
+ "en-core-web-trf",
37
+ ]
38
+ test = [
39
+ "hypothesis>=6.165.9",
40
+ "pytest>=9.1.1",
41
+ "pytest-cov>=7.1.0",
42
+ ]
43
+ typing = ["basedpyright>=1.39.9"]
44
+
45
+ [tool.uv.sources.en-core-web-sm]
46
+ url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl"
47
+
48
+ [tool.uv.sources.en-core-web-lg]
49
+ url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl"
50
+
51
+ [tool.uv.sources.en-core-web-trf]
52
+ url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_trf-3.8.0/en_core_web_trf-3.8.0-py3-none-any.whl"
53
+
54
+ [tool.ruff]
55
+ line-length = 88
56
+ src = ["src"]
57
+
58
+ [tool.ruff.lint]
59
+ select = [
60
+ "A",
61
+ "ANN",
62
+ "ARG",
63
+ "ASYNC",
64
+ "B",
65
+ "BLE",
66
+ "C4",
67
+ "C90",
68
+ "D",
69
+ "DTZ",
70
+ "E",
71
+ "ERA",
72
+ "EXE",
73
+ "F",
74
+ "FBT",
75
+ "FLY",
76
+ "FURB",
77
+ "G",
78
+ "I",
79
+ "LOG",
80
+ "N",
81
+ "PERF",
82
+ "PGH",
83
+ "PIE",
84
+ "PLC",
85
+ "PLE",
86
+ "PLW",
87
+ "PT",
88
+ "PTH",
89
+ "PYI",
90
+ "RET",
91
+ "RSE",
92
+ "RUF",
93
+ "SIM",
94
+ "SLOT",
95
+ "T20",
96
+ "UP",
97
+ "W",
98
+ "YTT",
99
+ ]
100
+ ignore = [
101
+ "D200",
102
+ "D212",
103
+ "PLC0415",
104
+ ]
105
+ allowed-confusables = [
106
+ "–",
107
+ "’",
108
+ "‘",
109
+ "‐",
110
+ "‑",
111
+ ]
112
+
113
+ [tool.ruff.lint.pycodestyle]
114
+ max-doc-length = 88
115
+
116
+ [tool.ruff.lint.pydocstyle]
117
+ convention = "google"
118
+
119
+ [tool.basedpyright]
120
+ typeCheckingMode = "recommended"
121
+ extraPaths = [
122
+ "tests",
123
+ "research",
124
+ ]
125
+
126
+ [tool.pytest.ini_options]
127
+ testpaths = ["tests"]
128
+ addopts = [
129
+ "--import-mode=importlib",
130
+ "--strict-markers",
131
+ ]
132
+ markers = ["pipeline: exercises a search through a real pipeline"]
133
+ pythonpath = ["tests"]
134
+
135
+ [tool.coverage.run]
136
+ source = ["src"]
137
+ branch = true
@@ -0,0 +1,143 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.12.3,<0.13"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "kwic"
7
+ version = "0.1.0"
8
+ description = "Key Word In Context"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Eva", email = "eva.evangelisti03@gmail.com" }]
14
+ dependencies = [
15
+ "spacy>=3.8.15",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ lemminflect = ["lemminflect>=0.2.3"]
20
+ stanza = ["stanza>=1.14.0"]
21
+
22
+ [project.urls]
23
+ Repository = "https://github.com/evaevangelisti/kwic"
24
+
25
+ [dependency-groups]
26
+ docs = [
27
+ "myst-parser>=5.1.0",
28
+ "shibuya>=2026.7.12",
29
+ "sphinx>=9.1.0",
30
+ ]
31
+
32
+ lint = [
33
+ "ruff>=0.16.2",
34
+ ]
35
+
36
+ models = [
37
+ "en-core-web-lg",
38
+ "en-core-web-sm",
39
+ "en-core-web-trf",
40
+ ]
41
+
42
+ test = [
43
+ "hypothesis>=6.165.9",
44
+ "pytest>=9.1.1",
45
+ "pytest-cov>=7.1.0",
46
+ ]
47
+
48
+ typing = [
49
+ "basedpyright>=1.39.9",
50
+ ]
51
+
52
+ # The pipelines are published as wheels off their own releases rather than to
53
+ # an index, so a lock file has to be told where each one is.
54
+ [tool.uv.sources]
55
+ en-core-web-sm = { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl" }
56
+ en-core-web-lg = { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_lg-3.8.0/en_core_web_lg-3.8.0-py3-none-any.whl" }
57
+ en-core-web-trf = { url = "https://github.com/explosion/spacy-models/releases/download/en_core_web_trf-3.8.0/en_core_web_trf-3.8.0-py3-none-any.whl" }
58
+
59
+ [tool.ruff]
60
+ line-length = 88
61
+ src = ["src"]
62
+
63
+ [tool.ruff.lint]
64
+ select = [
65
+ "A", # flake8-builtins
66
+ "ANN", # flake8-annotations
67
+ "ARG", # flake8-unused-arguments
68
+ "ASYNC", # flake8-async
69
+ "B", # flake8-bugbear
70
+ "BLE", # flake8-blind-except
71
+ "C4", # flake8-comprehensions
72
+ "C90", # mccabe
73
+ "D", # pydocstyle
74
+ "DTZ", # flake8-datetimez
75
+ "E", # pycodestyle, errors
76
+ "ERA", # eradicate
77
+ "EXE", # flake8-executable
78
+ "F", # Pyflakes
79
+ "FBT", # flake8-boolean-trap
80
+ "FLY", # flynt
81
+ "FURB", # refurb
82
+ "G", # flake8-logging-format
83
+ "I", # isort
84
+ "LOG", # flake8-logging
85
+ "N", # pep8-naming
86
+ "PERF", # Perflint
87
+ "PGH", # pygrep-hooks
88
+ "PIE", # flake8-pie
89
+ "PLC", # Pylint, conventions
90
+ "PLE", # Pylint, errors
91
+ "PLW", # Pylint, warnings
92
+ "PT", # flake8-pytest-style
93
+ "PTH", # flake8-use-pathlib
94
+ "PYI", # flake8-pyi
95
+ "RET", # flake8-return
96
+ "RSE", # flake8-raise
97
+ "RUF", # Ruff-specific rules
98
+ "SIM", # flake8-simplify
99
+ "SLOT", # flake8-slots
100
+ "T20", # flake8-print
101
+ "UP", # pyupgrade
102
+ "W", # pycodestyle, warnings
103
+ "YTT", # flake8-2020
104
+ ]
105
+
106
+ ignore = [
107
+ "D200", # A docstring is written across three lines, however short.
108
+ "D212", # Its summary opens on the line below the quotes.
109
+ "PLC0415", # An engine imports what it wraps where it loads it, not before.
110
+ ]
111
+
112
+ # A text sets its apostrophes and its hyphens typographically, which is what
113
+ # the search unifies before it reads a lemma back out of one.
114
+ allowed-confusables = ["–", "’", "‘", "‐", "‑"]
115
+
116
+ [tool.ruff.lint.pycodestyle]
117
+ max-doc-length = 88
118
+
119
+ [tool.ruff.lint.pydocstyle]
120
+ convention = "google"
121
+
122
+ [tool.basedpyright]
123
+ typeCheckingMode = "recommended"
124
+
125
+ # Where the suite and the studies keep what they share, which pytest and the
126
+ # scripts put on the path too.
127
+ extraPaths = ["tests", "research"]
128
+
129
+ [tool.pytest.ini_options]
130
+ testpaths = ["tests"]
131
+ addopts = ["--import-mode=importlib", "--strict-markers"]
132
+
133
+ # A pipeline is loaded once and read by every property, which is slow enough
134
+ # to be worth naming so that a run can leave it out.
135
+ markers = ["pipeline: exercises a search through a real pipeline"]
136
+
137
+ # The generators and the contexts the suite shares are written once, beside
138
+ # the tests drawing from them.
139
+ pythonpath = ["tests"]
140
+
141
+ [tool.coverage.run]
142
+ source = ["src"]
143
+ branch = true
@@ -0,0 +1,19 @@
1
+ """
2
+ Locating the lemmas of a query in the contexts that attest them.
3
+ """
4
+
5
+ from .engines import Engine, SpacyEngine
6
+ from .models import POS, Context, Match, Offsets, Query, Token
7
+ from .search import Locator
8
+
9
+ __all__ = [
10
+ "POS",
11
+ "Context",
12
+ "Engine",
13
+ "Locator",
14
+ "Match",
15
+ "Offsets",
16
+ "Query",
17
+ "SpacyEngine",
18
+ "Token",
19
+ ]
@@ -0,0 +1,18 @@
1
+ """
2
+ Values a search never varies.
3
+ """
4
+
5
+ SPACY_PIPELINE = "en_core_web_sm"
6
+ """The spaCy pipeline loaded unless another is named. What the larger English
7
+ pipelines add is vectors, which a search does not read."""
8
+
9
+ STANZA_LANGUAGE = "en"
10
+ """The language Stanza loads its processors for unless another is named."""
11
+
12
+ BATCH_SIZE = 256
13
+ """How many contexts a model reads at a time. Larger batches amortise the call
14
+ into the model and hold more contexts in memory."""
15
+
16
+ PROCESSES = 1
17
+ """How many processes a pipeline may run. More than one pays off over long
18
+ runs alone, each having to load the model again."""
@@ -0,0 +1,14 @@
1
+ """
2
+ The analysers a search may read with.
3
+
4
+ Only spaCy is loaded from here, it being the one dependency the package
5
+ takes. The others are extras, imported from the module that wraps them.
6
+ """
7
+
8
+ from .base import Engine
9
+ from .spacy import SpacyEngine
10
+
11
+ __all__ = [
12
+ "Engine",
13
+ "SpacyEngine",
14
+ ]