hanky 0.3.0.dev0__tar.gz → 0.3.0.dev2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hanky
3
- Version: 0.3.0.dev0
3
+ Version: 0.3.0.dev2
4
4
  Summary: Simple library and command line tool for loading flash cards into anki.
5
5
  Project-URL: Homepage, https://github.com/Haeata-Ash/hanky
6
6
  Project-URL: Issues, https://github.com/Haeata-Ash/hanky/issues
@@ -68,18 +68,18 @@ See [Card Processors & Pipelines](#card-processors--pipelines) or the [demo fold
68
68
  from hanky import HankyPipeline
69
69
 
70
70
 
71
- # instantiate the hanky app
72
- hanky = HankyPipeline()
71
+ # instantiate the hanky app, creating cards with the "basic" model
72
+ hanky = HankyPipeline("basic")
73
73
 
74
74
 
75
- @hanky.card_processor("basic", expected_args=[], card_fields=[])
75
+ @hanky.card_processor(expected_args=[], card_fields=[])
76
76
  def lowercase_card(card: dict):
77
77
  """Lower-case the text on every field of the card."""
78
78
  return {field: value.lower() for field, value in card.items()}
79
79
 
80
80
 
81
81
  # run the hanky cli application by running this python file, for example:
82
- # python3 my_script.py pipe words.csv --model basic --into english::vocab
82
+ # python3 my_script.py pipe words.csv --into english::vocab
83
83
  hanky.run()
84
84
 
85
85
  ```
@@ -100,10 +100,10 @@ Ephemeral,Lasting A VERY Short Time
100
100
  We would run our new script like so
101
101
 
102
102
  ```sh
103
- python3 my_script.py pipe words.csv --model basic --into english::vocab
103
+ python3 my_script.py pipe words.csv --into english::vocab
104
104
  ```
105
105
 
106
- Here we tell hanky to **pipe** each word in `words.csv` through our `card_processors`, **transforming** each line of the csv with the `lowercase_card`, before finally adding each card, of note type `basic`, into the anki deck `english::vocab`.
106
+ Here we tell hanky to **pipe** each word in `words.csv` through our `card_processors`, **transforming** each line of the csv with the `lowercase_card`, before finally adding each card, of note type `basic` (the model we gave to the pipeline), into the anki deck `english::vocab`.
107
107
 
108
108
  This would leave us with a **english::vocab** deck containing the following cards:
109
109
 
@@ -155,7 +155,7 @@ over the file). Useful if you want different config for different scripts:
155
155
  from hanky import HankyPipeline
156
156
  from hanky.config import Config
157
157
 
158
- hanky = HankyPipeline(config=Config(ALLOW_DUPLICATES=True))
158
+ hanky = HankyPipeline("basic", config=Config(ALLOW_DUPLICATES=True))
159
159
  ```
160
160
 
161
161
  ## Card Processors & Pipelines
@@ -168,17 +168,16 @@ Multiple processors can be registered on a `HankyPipeline` app to create a pipel
168
168
 
169
169
  ### The processor contract
170
170
 
171
- A processor is registered with three things:
171
+ A processor is registered with two things:
172
172
 
173
173
  ```python
174
- @hanky.card_processor(model, expected_args=[...], card_fields=[...])
174
+ @hanky.card_processor(expected_args=[...], card_fields=[...])
175
175
  def my_processor(card: dict, **expected_args):
176
176
  ...
177
177
  ```
178
178
 
179
179
  | Part | Meaning |
180
180
  | --- | --- |
181
- | `model` | The Anki model/note-type name. The processor runs on every card of this model. |
182
181
  | `expected_args` | Names of CLI arguments the processor needs. They are passed in from the command line via `--args key=value` and arrive as keyword arguments. For example, you might have the same pipeline for different languages, so you would pass in `lang=german` or `lang=french`|
183
182
  | `card_fields` | Fields that **must already be present** on the card when this processor runs. Hanky checks this and raises a clear error if one is missing. It lets a processor declare what an *earlier* step must have produced. |
184
183
 
@@ -186,7 +185,7 @@ When hanky calls your processors, the first argument is always the `card`; a pla
186
185
 
187
186
 
188
187
  ```python
189
- @hanky.card_processor(model, expected_args=["lang"], card_fields=[...])
188
+ @hanky.card_processor(expected_args=["lang"], card_fields=[...])
190
189
  def my_processor(card: dict, lang):
191
190
  ...
192
191
  ```
@@ -240,10 +239,10 @@ def scrape_wordreference(word: str, lang_pair: str) -> tuple[str, str]:
240
239
  def generate_neural_speech(utf_8_str: str, voice: str) -> bytes:
241
240
  ...
242
241
 
243
- hanky = HankyPipeline()
242
+ hanky = HankyPipeline("lang-vocab")
244
243
 
245
244
  # Stage 1: requires `word` (this comes straight from the csv), produces `translation` + `example`.
246
- @hanky.card_processor("lang-vocab", expected_args=[], card_fields=["word"])
245
+ @hanky.card_processor(expected_args=[], card_fields=["word"])
247
246
  def scrape_translation(card: dict):
248
247
  translation, example = scrape_wordreference(card["word"], "enfr")
249
248
  card["translation"] = translation
@@ -251,7 +250,7 @@ def scrape_translation(card: dict):
251
250
  return card
252
251
 
253
252
  # Stage 2: requires `translation` (from stage 1), attaches audio media.
254
- @hanky.card_processor("lang-vocab", expected_args=[], card_fields=["translation"])
253
+ @hanky.card_processor(expected_args=[], card_fields=["translation"])
255
254
  def add_audio(card: dict):
256
255
  speech = generate_neural_speech(card["translation"], voice="Lea")
257
256
  audio = CardMedia(speech, ".mp3")
@@ -261,9 +260,9 @@ def add_audio(card: dict):
261
260
  hanky.run()
262
261
  ```
263
262
 
264
- Then we would run the script like normal. `python3 my_script.py pipe words.xlsx --model lang-vocab --into french::vocab`
263
+ Then we would run the script like normal. `python3 my_script.py pipe words.xlsx --into french::vocab`
265
264
 
266
- > Note that we are no longer using a *basic* model. That means we are assuming that a model called *lang-vocab* has already been created in the anki ui. See the [Anki documentation for adding a note type](https://docs.ankiweb.net/editing.html#adding-a-note-type) to learn how this is done.
265
+ > Note that this pipeline was created with the *lang-vocab* model rather than *basic*. That means we are assuming that a model called *lang-vocab* has already been created in the anki ui. See the [Anki documentation for adding a note type](https://docs.ankiweb.net/editing.html#adding-a-note-type) to learn how this is done.
267
266
 
268
267
 
269
268
  ### Attaching media
@@ -322,7 +321,7 @@ DICTIONARY = {
322
321
  # ...
323
322
  }
324
323
 
325
- hanky = HankyPipeline()
324
+ hanky = HankyPipeline("basic")
326
325
 
327
326
 
328
327
  def random_word_cards(n):
@@ -331,7 +330,7 @@ def random_word_cards(n):
331
330
 
332
331
 
333
332
  # add 20 cards straight from the generator, with no file involved
334
- report = hanky.import_from_source(random_word_cards(20), "basic", "english::vocab")
333
+ report = hanky.import_from_source(random_word_cards(20), "english::vocab")
335
334
  print(f"Added {report.added}, skipped {report.skipped}, failed {report.failed}.")
336
335
  ```
337
336
 
@@ -367,15 +366,19 @@ from simplest to most involved. Install their dependencies with
367
366
  Both the `hanky` command and your own scripts share the same interface:
368
367
 
369
368
  ```
370
- [hanky | python3 my_script.py] <operation> <file|dir> [pattern] --model MODEL [options]
369
+ [hanky | python3 my_script.py] <operation> <file|dir> [pattern] [options]
371
370
  ```
372
371
 
372
+ Note that cards are created with the model set on the pipeline (`HankyPipeline("my-model")`).
373
+ Since standalone `hanky` command has no script of its own, it uses Anki's built-in
374
+ `Basic` model unless you override it.
375
+
373
376
  **`pipe`** — pipe cards from a single file into a deck:
374
377
 
375
378
  ```
376
- hanky pipe [-h] -m MODEL [--into DECK] [--fail-fast] [--args K=V ...] file
379
+ hanky pipe [-h] [-m MODEL] [--into DECK] [--fail-fast] [--args K=V ...] file
377
380
  file File to load from (.csv, .json, or a registered extension).
378
- -m, --model Anki model/note-type name to create cards with. Required.
381
+ -m, --model Override the Anki model/note-type name to create cards with.
379
382
  --into Destination deck. Defaults to the filename without extension.
380
383
  --fail-fast Stop and raise on the first card that can't be added, instead
381
384
  of skipping it and reporting it at the end.
@@ -385,10 +388,10 @@ hanky pipe [-h] -m MODEL [--into DECK] [--fail-fast] [--args K=V ...] file
385
388
  **`pipe-dir`** — pipe many files from a directory, deriving deck names from paths:
386
389
 
387
390
  ```
388
- hanky pipe-dir [-h] -m MODEL [-r] [--fail-fast] [--args K=V ...] dir pattern
391
+ hanky pipe-dir [-h] [-m MODEL] [-r] [--fail-fast] [--args K=V ...] dir pattern
389
392
  dir Directory to load from.
390
393
  pattern Glob selecting files, e.g. "*.csv".
391
- -m, --model Anki model/note-type name to create cards with. Required.
394
+ -m, --model Override the Anki model/note-type name to create cards with.
392
395
  -r, --recursive Also descend into sub-directories.
393
396
  --fail-fast Stop and raise on the first card that can't be added, instead
394
397
  of skipping it and reporting it at the end.
@@ -399,7 +402,7 @@ For example, to load every CSV under `french/` while building deck names from
399
402
  the folder structure:
400
403
 
401
404
  ```sh
402
- hanky pipe-dir "french/" "*.csv" --model basic -r
405
+ hanky pipe-dir "french/" "*.csv" -r
403
406
  ```
404
407
 
405
408
  ```
@@ -50,18 +50,18 @@ See [Card Processors & Pipelines](#card-processors--pipelines) or the [demo fold
50
50
  from hanky import HankyPipeline
51
51
 
52
52
 
53
- # instantiate the hanky app
54
- hanky = HankyPipeline()
53
+ # instantiate the hanky app, creating cards with the "basic" model
54
+ hanky = HankyPipeline("basic")
55
55
 
56
56
 
57
- @hanky.card_processor("basic", expected_args=[], card_fields=[])
57
+ @hanky.card_processor(expected_args=[], card_fields=[])
58
58
  def lowercase_card(card: dict):
59
59
  """Lower-case the text on every field of the card."""
60
60
  return {field: value.lower() for field, value in card.items()}
61
61
 
62
62
 
63
63
  # run the hanky cli application by running this python file, for example:
64
- # python3 my_script.py pipe words.csv --model basic --into english::vocab
64
+ # python3 my_script.py pipe words.csv --into english::vocab
65
65
  hanky.run()
66
66
 
67
67
  ```
@@ -82,10 +82,10 @@ Ephemeral,Lasting A VERY Short Time
82
82
  We would run our new script like so
83
83
 
84
84
  ```sh
85
- python3 my_script.py pipe words.csv --model basic --into english::vocab
85
+ python3 my_script.py pipe words.csv --into english::vocab
86
86
  ```
87
87
 
88
- Here we tell hanky to **pipe** each word in `words.csv` through our `card_processors`, **transforming** each line of the csv with the `lowercase_card`, before finally adding each card, of note type `basic`, into the anki deck `english::vocab`.
88
+ Here we tell hanky to **pipe** each word in `words.csv` through our `card_processors`, **transforming** each line of the csv with the `lowercase_card`, before finally adding each card, of note type `basic` (the model we gave to the pipeline), into the anki deck `english::vocab`.
89
89
 
90
90
  This would leave us with a **english::vocab** deck containing the following cards:
91
91
 
@@ -137,7 +137,7 @@ over the file). Useful if you want different config for different scripts:
137
137
  from hanky import HankyPipeline
138
138
  from hanky.config import Config
139
139
 
140
- hanky = HankyPipeline(config=Config(ALLOW_DUPLICATES=True))
140
+ hanky = HankyPipeline("basic", config=Config(ALLOW_DUPLICATES=True))
141
141
  ```
142
142
 
143
143
  ## Card Processors & Pipelines
@@ -150,17 +150,16 @@ Multiple processors can be registered on a `HankyPipeline` app to create a pipel
150
150
 
151
151
  ### The processor contract
152
152
 
153
- A processor is registered with three things:
153
+ A processor is registered with two things:
154
154
 
155
155
  ```python
156
- @hanky.card_processor(model, expected_args=[...], card_fields=[...])
156
+ @hanky.card_processor(expected_args=[...], card_fields=[...])
157
157
  def my_processor(card: dict, **expected_args):
158
158
  ...
159
159
  ```
160
160
 
161
161
  | Part | Meaning |
162
162
  | --- | --- |
163
- | `model` | The Anki model/note-type name. The processor runs on every card of this model. |
164
163
  | `expected_args` | Names of CLI arguments the processor needs. They are passed in from the command line via `--args key=value` and arrive as keyword arguments. For example, you might have the same pipeline for different languages, so you would pass in `lang=german` or `lang=french`|
165
164
  | `card_fields` | Fields that **must already be present** on the card when this processor runs. Hanky checks this and raises a clear error if one is missing. It lets a processor declare what an *earlier* step must have produced. |
166
165
 
@@ -168,7 +167,7 @@ When hanky calls your processors, the first argument is always the `card`; a pla
168
167
 
169
168
 
170
169
  ```python
171
- @hanky.card_processor(model, expected_args=["lang"], card_fields=[...])
170
+ @hanky.card_processor(expected_args=["lang"], card_fields=[...])
172
171
  def my_processor(card: dict, lang):
173
172
  ...
174
173
  ```
@@ -222,10 +221,10 @@ def scrape_wordreference(word: str, lang_pair: str) -> tuple[str, str]:
222
221
  def generate_neural_speech(utf_8_str: str, voice: str) -> bytes:
223
222
  ...
224
223
 
225
- hanky = HankyPipeline()
224
+ hanky = HankyPipeline("lang-vocab")
226
225
 
227
226
  # Stage 1: requires `word` (this comes straight from the csv), produces `translation` + `example`.
228
- @hanky.card_processor("lang-vocab", expected_args=[], card_fields=["word"])
227
+ @hanky.card_processor(expected_args=[], card_fields=["word"])
229
228
  def scrape_translation(card: dict):
230
229
  translation, example = scrape_wordreference(card["word"], "enfr")
231
230
  card["translation"] = translation
@@ -233,7 +232,7 @@ def scrape_translation(card: dict):
233
232
  return card
234
233
 
235
234
  # Stage 2: requires `translation` (from stage 1), attaches audio media.
236
- @hanky.card_processor("lang-vocab", expected_args=[], card_fields=["translation"])
235
+ @hanky.card_processor(expected_args=[], card_fields=["translation"])
237
236
  def add_audio(card: dict):
238
237
  speech = generate_neural_speech(card["translation"], voice="Lea")
239
238
  audio = CardMedia(speech, ".mp3")
@@ -243,9 +242,9 @@ def add_audio(card: dict):
243
242
  hanky.run()
244
243
  ```
245
244
 
246
- Then we would run the script like normal. `python3 my_script.py pipe words.xlsx --model lang-vocab --into french::vocab`
245
+ Then we would run the script like normal. `python3 my_script.py pipe words.xlsx --into french::vocab`
247
246
 
248
- > Note that we are no longer using a *basic* model. That means we are assuming that a model called *lang-vocab* has already been created in the anki ui. See the [Anki documentation for adding a note type](https://docs.ankiweb.net/editing.html#adding-a-note-type) to learn how this is done.
247
+ > Note that this pipeline was created with the *lang-vocab* model rather than *basic*. That means we are assuming that a model called *lang-vocab* has already been created in the anki ui. See the [Anki documentation for adding a note type](https://docs.ankiweb.net/editing.html#adding-a-note-type) to learn how this is done.
249
248
 
250
249
 
251
250
  ### Attaching media
@@ -304,7 +303,7 @@ DICTIONARY = {
304
303
  # ...
305
304
  }
306
305
 
307
- hanky = HankyPipeline()
306
+ hanky = HankyPipeline("basic")
308
307
 
309
308
 
310
309
  def random_word_cards(n):
@@ -313,7 +312,7 @@ def random_word_cards(n):
313
312
 
314
313
 
315
314
  # add 20 cards straight from the generator, with no file involved
316
- report = hanky.import_from_source(random_word_cards(20), "basic", "english::vocab")
315
+ report = hanky.import_from_source(random_word_cards(20), "english::vocab")
317
316
  print(f"Added {report.added}, skipped {report.skipped}, failed {report.failed}.")
318
317
  ```
319
318
 
@@ -349,15 +348,19 @@ from simplest to most involved. Install their dependencies with
349
348
  Both the `hanky` command and your own scripts share the same interface:
350
349
 
351
350
  ```
352
- [hanky | python3 my_script.py] <operation> <file|dir> [pattern] --model MODEL [options]
351
+ [hanky | python3 my_script.py] <operation> <file|dir> [pattern] [options]
353
352
  ```
354
353
 
354
+ Note that cards are created with the model set on the pipeline (`HankyPipeline("my-model")`).
355
+ Since standalone `hanky` command has no script of its own, it uses Anki's built-in
356
+ `Basic` model unless you override it.
357
+
355
358
  **`pipe`** — pipe cards from a single file into a deck:
356
359
 
357
360
  ```
358
- hanky pipe [-h] -m MODEL [--into DECK] [--fail-fast] [--args K=V ...] file
361
+ hanky pipe [-h] [-m MODEL] [--into DECK] [--fail-fast] [--args K=V ...] file
359
362
  file File to load from (.csv, .json, or a registered extension).
360
- -m, --model Anki model/note-type name to create cards with. Required.
363
+ -m, --model Override the Anki model/note-type name to create cards with.
361
364
  --into Destination deck. Defaults to the filename without extension.
362
365
  --fail-fast Stop and raise on the first card that can't be added, instead
363
366
  of skipping it and reporting it at the end.
@@ -367,10 +370,10 @@ hanky pipe [-h] -m MODEL [--into DECK] [--fail-fast] [--args K=V ...] file
367
370
  **`pipe-dir`** — pipe many files from a directory, deriving deck names from paths:
368
371
 
369
372
  ```
370
- hanky pipe-dir [-h] -m MODEL [-r] [--fail-fast] [--args K=V ...] dir pattern
373
+ hanky pipe-dir [-h] [-m MODEL] [-r] [--fail-fast] [--args K=V ...] dir pattern
371
374
  dir Directory to load from.
372
375
  pattern Glob selecting files, e.g. "*.csv".
373
- -m, --model Anki model/note-type name to create cards with. Required.
376
+ -m, --model Override the Anki model/note-type name to create cards with.
374
377
  -r, --recursive Also descend into sub-directories.
375
378
  --fail-fast Stop and raise on the first card that can't be added, instead
376
379
  of skipping it and reporting it at the end.
@@ -381,7 +384,7 @@ For example, to load every CSV under `french/` while building deck names from
381
384
  the folder structure:
382
385
 
383
386
  ```sh
384
- hanky pipe-dir "french/" "*.csv" --model basic -r
387
+ hanky pipe-dir "french/" "*.csv" -r
385
388
  ```
386
389
 
387
390
  ```
@@ -16,7 +16,7 @@ classifiers = [
16
16
  ]
17
17
 
18
18
  keywords = ["anki"]
19
- version = "0.3.0.dev0"
19
+ version = "0.3.0.dev2"
20
20
 
21
21
  dependencies = [
22
22
  "anki",
@@ -6,7 +6,8 @@ from hanky.errors import HankyError
6
6
 
7
7
  def main():
8
8
  try:
9
- hanky = HankyPipeline()
9
+ # default to basic model
10
+ hanky = HankyPipeline("basic")
10
11
  hanky.run()
11
12
  except HankyError as e:
12
13
  print(f"error: {e}", file=sys.stderr)
@@ -20,6 +20,17 @@ def _add_fail_fast(subparser):
20
20
  )
21
21
 
22
22
 
23
+ def _add_model_override(subparser):
24
+ subparser.add_argument(
25
+ "-m",
26
+ "--model",
27
+ dest="model",
28
+ required=False,
29
+ default=None,
30
+ help="Override the name of the anki model to create cards with.",
31
+ )
32
+
33
+
23
34
  def _add_processor_args(subparser):
24
35
  subparser.add_argument(
25
36
  "--args",
@@ -45,13 +56,7 @@ def make_parser(has_card_processors=False):
45
56
  "pipe", help="Pipe card(s) from a file into an anki deck."
46
57
  )
47
58
  pipe.add_argument("file", help="Path of the file to load from")
48
- pipe.add_argument(
49
- "-m",
50
- "--model",
51
- dest="model",
52
- required=True,
53
- help="Name of the anki model to create cards with.",
54
- )
59
+ _add_model_override(pipe)
55
60
  pipe.add_argument(
56
61
  "--into",
57
62
  dest="deck",
@@ -71,13 +76,7 @@ def make_parser(has_card_processors=False):
71
76
  "pattern",
72
77
  help="Glob pattern used to decide which files to load. For example, '*.csv'",
73
78
  )
74
- pipe_dir.add_argument(
75
- "-m",
76
- "--model",
77
- dest="model",
78
- required=True,
79
- help="Name of the anki model to create cards with.",
80
- )
79
+ _add_model_override(pipe_dir)
81
80
  pipe_dir.add_argument(
82
81
  "-r",
83
82
  "--recursive",
@@ -14,7 +14,7 @@ from typing import (
14
14
  from anki.collection import Collection
15
15
 
16
16
  from hanky.anki_utils import add_card, add_deck, add_media, backup_collection
17
- from hanky.processors import CardProcessingException, ModelProcessor
17
+ from hanky.processors import CardProcessingException, CardProcessor
18
18
  from hanky.cli import make_parser
19
19
  from hanky.config import Config
20
20
  from hanky.errors import (
@@ -35,31 +35,40 @@ class HankyPipeline:
35
35
  interactions with the anki collection and exposes a simplified interface
36
36
  for adding transformation logic. Optionally runnable as a CLI application.
37
37
 
38
+ Cards are added using the anki model/note type the pipeline was constructed
39
+ with (a single CLI run can override it via --model).
40
+
38
41
  Keeps track of 'card processor' functions/callables which enrich or
39
42
  transform their data before adding the card to the database.
40
43
 
41
44
  Keeps track of 'loader' functions which read possibly incomplete card data from files.
42
45
 
43
46
  Attributes:
44
- config: dictionary representing configuration and kwargs arguments
45
- processors: dictionary of anki model names mapped to user defined callables
47
+ config: the pipeline's Config object, lazy loaded if not provided
48
+ processors: list of user defined card processor callables, in registration order
46
49
  loaders: dictionary of file extensions mapped to a function which reads card data
47
50
  """
48
51
 
49
- def __init__(self, config: Optional[Config] = None):
52
+ def __init__(self, model: str, *, config: Optional[Config] = None):
50
53
  """Initializes a HankyPipeline application object
51
54
 
52
55
  Note: if no config object is provided, hanky will try load from the default config location,
53
56
  before finally using the default configuration values if the file does not exist.
54
57
 
55
58
  Args:
59
+ model: the name of the anki model/note type to create cards with
56
60
  config: custom config object
57
61
  """
62
+ if not isinstance(model, str):
63
+ raise TypeError(
64
+ "'model' must be the name of an anki model/note type (a string)"
65
+ )
66
+
58
67
  self._config = config
59
68
 
60
69
  self._col: Optional[Collection] = None
61
-
62
- self.processors: Dict[str, List[ModelProcessor]] = dict()
70
+ self._model = model
71
+ self.processors: List[CardProcessor] = list()
63
72
  self.loaders: Dict[str, Callable[[str], Iterator[dict]]] = dict()
64
73
  for ext, spec in DEFAULT_LOADERS.items():
65
74
  self.register_loader(
@@ -167,12 +176,11 @@ class HankyPipeline:
167
176
 
168
177
  def register_card_processor(
169
178
  self,
170
- model_name: str,
171
179
  processor: Callable[[dict], dict],
172
180
  expected_args: List[str] = [],
173
181
  card_fields: List[str] = [],
174
182
  ) -> None:
175
- """Adds a python callable to be called when adding a card of type model.
183
+ """Adds a python callable to be called as a processor during a pipeline run.
176
184
 
177
185
  The callable will be called with the first argument being the card
178
186
  data (a dictionary mapping field names to values) BEFORE the card
@@ -183,8 +191,7 @@ class HankyPipeline:
183
191
  order in which they were registered.
184
192
 
185
193
  Args:
186
- model_name: the name of the card model
187
- processor: the callable to apply to the cards of the given model type
194
+ processor: the callable to apply to each card
188
195
  expected_args: list of arguments expected by the callable
189
196
  card_fields: list of fields expected to be present in the card
190
197
  when the callable is applied
@@ -192,15 +199,9 @@ class HankyPipeline:
192
199
  Returns:
193
200
  None
194
201
  """
195
- if model_name not in self.processors:
196
- self.processors[model_name] = []
197
- self.processors[model_name].append(
198
- ModelProcessor(processor, expected_args, card_fields)
199
- )
202
+ self.processors.append(CardProcessor(processor, expected_args, card_fields))
200
203
 
201
- def card_processor(
202
- self, model: str, expected_args: List[str], card_fields: List[str]
203
- ):
204
+ def card_processor(self, expected_args: List[str], card_fields: List[str]):
204
205
  """Decorator which automatically registers a card processor function
205
206
 
206
207
  A card processor takes a card (dictionary of field, value pairs) and any
@@ -212,14 +213,14 @@ class HankyPipeline:
212
213
  - perform a mathmatical operation then write back the answer as a string
213
214
  - set a field of a card which is currently missing
214
215
 
215
- The decoracted function will be called every time a card of type model
216
- is added. The first argument to the decorated function will always be
217
- the card data as a dictionary of fields, value pairs.
216
+ The decorated function will be called on every card each time the pipeline
217
+ is run, whether via the CLI or the import_from_* methods. The first argument
218
+ to the decorated function will always be the card data as a dictionary of
219
+ fields, value pairs.
218
220
 
219
221
  The decorated function must return a dictionary.
220
222
 
221
223
  Args:
222
- model_name: the name of the card model
223
224
  expected_args: list of named arguments expected by the card processor.
224
225
  They will be passed in as key word arguments.
225
226
  card_fields: list of fields expected to be present in the card
@@ -230,18 +231,11 @@ class HankyPipeline:
230
231
  """
231
232
 
232
233
  def decorator(func):
233
- self.register_card_processor(model, func, expected_args, card_fields)
234
+ self.register_card_processor(func, expected_args, card_fields)
234
235
  return func
235
236
 
236
237
  return decorator
237
238
 
238
- def get_model_processors(self, model_name: str) -> List[ModelProcessor]:
239
- """Get all card processors for a particular model"""
240
- if model_name in self.processors:
241
- return self.processors[model_name]
242
-
243
- return []
244
-
245
239
  def get_loader(self, suffix) -> Callable:
246
240
  """Get the loader function for a particular file extension"""
247
241
  suffix = suffix.lower()
@@ -257,7 +251,6 @@ class HankyPipeline:
257
251
  def import_from_source(
258
252
  self,
259
253
  source: Iterable[Mapping],
260
- model_name: str,
261
254
  deck_name: str,
262
255
  fail_fast: bool = False,
263
256
  **model_args,
@@ -271,7 +264,6 @@ class HankyPipeline:
271
264
  Args:
272
265
  source: any iterable yielding dictionaries (mappings) of card
273
266
  field names to values
274
- model_name: The anki model/card type of the cards
275
267
  deck_name: the name of the destination deck
276
268
  fail_fast: raise on the first bad card instead of collecting it
277
269
  **model_args: arguments to provide to the card processor functions
@@ -280,16 +272,15 @@ class HankyPipeline:
280
272
  A :class:`LoadReport` describing what was added, skipped and failed.
281
273
 
282
274
  Raises:
283
- ModelNotFoundError: the model does not exist in the collection
275
+ ModelNotFoundError: the pipeline's model does not exist in the collection
284
276
  Exception: if ``fail_fast`` is set, whatever a bad card raised
285
277
  """
286
- transformers = self.get_model_processors(model_name)
287
278
 
288
279
  with self.session() as col:
289
- model = col.models.by_name(model_name)
280
+ model = col.models.by_name(self._model)
290
281
  if not model:
291
282
  raise ModelNotFoundError(
292
- f"Model '{model_name}' does not exist in your anki collection. Ensure it has been added before using it with hanky."
283
+ f"Model '{self._model}' does not exist in your anki collection. Ensure it has been added before using it with hanky."
293
284
  )
294
285
 
295
286
  add_deck(col, deck_name)
@@ -301,12 +292,12 @@ class HankyPipeline:
301
292
  try:
302
293
  if not isinstance(item, Mapping):
303
294
  raise ValueError(
304
- f"Card source for model '{model_name}' yielded a "
295
+ f"Card source for model '{self._model}' yielded a "
305
296
  f"{type(item).__name__}, expected a dictionary (mapping)."
306
297
  )
307
298
  card = dict(item)
308
299
  media: List[CardMedia] = []
309
- for t in transformers:
300
+ for t in self.processors:
310
301
  card, new_media = t(card, **model_args)
311
302
  media += new_media
312
303
 
@@ -319,7 +310,7 @@ class HankyPipeline:
319
310
  if add_card(
320
311
  col,
321
312
  deck_name,
322
- model_name,
313
+ self._model,
323
314
  allow_duplicates=self.config.ALLOW_DUPLICATES,
324
315
  **card,
325
316
  ):
@@ -329,7 +320,7 @@ class HankyPipeline:
329
320
  except Exception as e:
330
321
  # inject model info into exception which processor doesn't know
331
322
  if isinstance(e, CardProcessingException):
332
- e.model = model_name
323
+ e.model = self._model
333
324
  if fail_fast:
334
325
  raise
335
326
  errors.append(CardError(card=item, error=str(e)))
@@ -339,7 +330,6 @@ class HankyPipeline:
339
330
  def import_from_file(
340
331
  self,
341
332
  fpath: str,
342
- model_name: str,
343
333
  deck_name: Optional[str] = None,
344
334
  fail_fast: bool = False,
345
335
  **model_args,
@@ -351,7 +341,6 @@ class HankyPipeline:
351
341
 
352
342
  Args:
353
343
  fpath: the path to the file
354
- model_name: The anki model/card type of the cards in the file
355
344
  deck_name: Optionally the name of the deck. Defaults to the
356
345
  filename without its extension.
357
346
  fail_fast: raise on the first bad card instead of collecting it
@@ -368,13 +357,12 @@ class HankyPipeline:
368
357
 
369
358
  source = self.get_loader(path.suffix)(str(path))
370
359
  report = self.import_from_source(
371
- source, model_name, deck_name, fail_fast=fail_fast, **model_args
360
+ source, deck_name, fail_fast=fail_fast, **model_args
372
361
  )
373
362
  return report.with_source(str(path))
374
363
 
375
364
  def import_from_dir(
376
365
  self,
377
- model: str,
378
366
  root_dir: str,
379
367
  glob_pattern: str,
380
368
  recursive=False,
@@ -402,7 +390,6 @@ class HankyPipeline:
402
390
  french::grammar::passe_compose
403
391
 
404
392
  Args:
405
- model_name: The anki model/card type of the cards which will be loaded
406
393
  root_dir: The root directory in which to find the files
407
394
  glob_pattern: A glob pattern such as '*.csv' to match the desired files
408
395
  recursive: whether or not to descend into sub directories, defaults to false
@@ -448,7 +435,6 @@ class HankyPipeline:
448
435
 
449
436
  report += self.import_from_file(
450
437
  str(abs_path),
451
- model,
452
438
  deck_name=full_deck,
453
439
  fail_fast=fail_fast,
454
440
  **model_args,
@@ -488,12 +474,15 @@ def _run_app(app: HankyPipeline, args: Optional[Sequence[str]] = None):
488
474
  except AttributeError:
489
475
  pass
490
476
 
477
+ # TODO: shouldn't really mutate _model like this
478
+ if parsed_args.model is not None:
479
+ app._model = parsed_args.model
480
+
491
481
  report = LoadReport()
492
482
  if parsed_args.operation == "pipe":
493
483
  print(f"Loading into deck {parsed_args.deck} from file {parsed_args.file}")
494
484
  report = app.import_from_file(
495
485
  parsed_args.file,
496
- parsed_args.model,
497
486
  deck_name=parsed_args.deck,
498
487
  fail_fast=parsed_args.fail_fast,
499
488
  **model_args,
@@ -502,7 +491,6 @@ def _run_app(app: HankyPipeline, args: Optional[Sequence[str]] = None):
502
491
  elif parsed_args.operation == "pipe-dir":
503
492
  print(f"Loading from dirrectory {parsed_args.dir}")
504
493
  report = app.import_from_dir(
505
- parsed_args.model,
506
494
  parsed_args.dir,
507
495
  parsed_args.pattern,
508
496
  parsed_args.is_rec,
@@ -30,7 +30,7 @@ class CardProcessingException(Exception):
30
30
  )
31
31
 
32
32
 
33
- class ModelProcessor:
33
+ class CardProcessor:
34
34
  """The wrapper for user defined functions which processes cards.
35
35
 
36
36
  Wraps a python callable which takes a dictionary representing an anki card,
@@ -49,7 +49,7 @@ class ModelProcessor:
49
49
  expected_args: List[str],
50
50
  card_fields: List[str],
51
51
  ):
52
- """Initializes a model processor
52
+ """Initializes a card processor
53
53
 
54
54
  Args:
55
55
  func: the user defined callable which processes each card
@@ -398,7 +398,7 @@ wheels = [
398
398
 
399
399
  [[package]]
400
400
  name = "hanky"
401
- version = "0.3.0.dev0"
401
+ version = "0.3.0.dev2"
402
402
  source = { editable = "." }
403
403
  dependencies = [
404
404
  { name = "anki" },
File without changes
File without changes
File without changes