Repository navigation
Read and transform source records from the TIMDEX dataset - #280
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Omitting --output-location for a dataset-only invocation leaves the writer’s location as None instead of defaulting to the input dataset path.
1 open issue
What changed in this PR
Adds support for re-transforming a source’s current indexed records directly from a TIMDEX dataset, without repeating the harvest step.
Changes:
- Adds dataset input to the CLI, with mutually exclusive file and dataset options.
- Reads stored source records, parses JSON or XML, and generates
patchrun metadata. - Adds parsing tests and updates existing tests and fixtures.
| File | Description |
|---|---|
| transmogrifier/sources/xmltransformer.py | Parses individual stored XML records. |
| transmogrifier/sources/transformer.py | Loads dataset records and generates dataset-backed run metadata. |
| transmogrifier/sources/jsontransformer.py | Parses stored JSON records and validates dictionary output. |
| transmogrifier/cli.py | Adds dataset input and validates input/output options. |
| tests/sources/test_transformer.py | Tests record parsing and updates metadata assertions. |
| tests/sources/json/test_libguides.py | Simplifies fixture formatting without changing behavior. |
| tests/conftest.py | Updates the generic fixture for the new abstract parsing method. |
Review effort: Balanced
ghukill
left a comment
There was a problem hiding this comment.
Full disclosure, I went into this quite nit-picky given that I'll be using this functionality soon and I think we may want to extend and build upon in later!
Overall, I think the approach is simple and elegant. 90% of my comments are about namings of things and docstrings, much of which is baggage from previous changes that just now tip into feeling like it's worth some attention.
| @click.option( | ||
| "-i", | ||
| "--input-file", | ||
| required=True, | ||
| help="Filepath of input records to transform. The filename must be in the format " | ||
| "<source>-<YYYY-MM-DD>-<run-type>-extracted-records-to-<action><index[optional]>" | ||
| ".<extension>. Examples: 'gisogm-2024-03-28-daily-extracted-records-to-index.jsonl' " | ||
| "or 'alma-2023-01-13-full-extracted-records-to-index_17.xml'.", | ||
| required=False, | ||
| help=( | ||
| "Filepath of input records to transform. The filename must be in the format " | ||
| "<source>-<YYYY-MM-DD>-<run-type>-extracted-records-to-<action><index[optional]>" | ||
| ".<extension>. Examples: 'gisogm-2024-03-28-daily-extracted-records-to-index.jsonl' " # noqa: E501 | ||
| "or 'alma-2023-01-13-full-extracted-records-to-index_17.xml'." | ||
| ), | ||
| ) | ||
| @click.option( | ||
| "-o", | ||
| "--output-location", | ||
| required=True, | ||
| help="Location of TIMDEX parquet dataset to write to.", | ||
| "-d", | ||
| "--dataset", | ||
| required=False, | ||
| help="Location of TIMDEX parquet dataset with source records to transform.", | ||
| ) | ||
| @click.option( | ||
| "-s", | ||
| "--source", | ||
| required=True, | ||
| type=click.Choice(list(SOURCES.keys()), case_sensitive=False), | ||
| help="Source records were harvested from, must choose from list of options", | ||
| "-o", | ||
| "--output-location", | ||
| required=False, | ||
| help="Location of TIMDEX dataset to write to.", |
There was a problem hiding this comment.
This comment is a catch-all for the CLI options.
As mentioned in a slack huddle, I think these arguments are getting a little confusing and overloaded. This is not the fault of this PR, as it was heading that direction when we started supporting writing to a TIMDEX dataset versus individual files.
One variable here is that I think these will continue to change in the coming weeks / months (e.g. the ability to read parquet files as input), so I'd propose that any changes now mostly serve to be explicit and obvious, making possible future additions or changes easier. And, gain a little bit of clarity now.
What do you think about the following set of arguments @jonavellecuerdo?
--input-file- unchanged/existing
- read an input file for parsing + transforming, could be local or S3
- this would remain the default assumed used argument in the StepFunction
--input-dataset- changed/new
- indicates that this TIMDEX dataset is used for input and nothing to do with the outputs of the transformed records
--output-file- changed/new
- explicitly states that output is to a local/S3 file
- this was the default mode pre TIMDEX dataset, is not any longer, but remains helpful for quick debugging
--ouput-dataset- changed/new
- explictily states that the output records are written to this TIMDEX dataset
This would require a small change in the pipeline lambdas: update --output-location to be --output-dataset. I'd propose that this is more explicit, easier to understand.
Why do all this?
- input validation is easier: one
--input-*and one--output-*arg required, and only one of each - if we support new input/output options, just add them, e.g.
--input-parquetand--output-parquet - part of the required work for this PR is an update to the pipeline lambdas already, so a decent time to align to the new
--output-datasetargument name
There was a problem hiding this comment.
Made changes that result in the following CLI options -- see the latest commit e8b700c:
--i / --input-file
--id / --input-dataset
--o / --output-dataset
I realized that we no longer have any write_*() methods on Transformer that write to a file; the only method on Transformer is write_to_parquet_dataset().
Are you requesting restoring a Transformer's ability to write to an output file (not just a TIMDEX dataset)? 🤔
There was a problem hiding this comment.
@jonavellecuerdo - reviewing now and just wanted to confirm: you opted to drop / not include --output-file as an option?
FWIW, I'm okay with that, given some work I have scoped and ready to PR. It will introduce a new --output-parquet which is honestly what I'd use over --output-file any day.
There was a problem hiding this comment.
I didn't included --output-file as an option because the Transformer class no longer has a method to write to a non-parquet dataset (as in a JSON Lines file) 🤔
There was a problem hiding this comment.
Righto! I couldn't remember where we landed on that, or if any supporting code existed. Sounds like it didn't. Agreed, not needed.
There was a problem hiding this comment.
I'd be remiss if I didn't add that I may propose adding this in one of my upcoming commits, to give parity with --input-file. If so, it's not a sleight on this PR! I think it was the right call to not add it now. My commit / PR will introduce --input-parquet and --output-parquet so it's building up a 3 option in, 3 option out setup.
| if (source_file and source_dataset) or (not source_file and not source_dataset): | ||
| raise ValueError( | ||
| "Exactly one input source is required: 'source_file' or 'source_dataset'" | ||
| ) |
There was a problem hiding this comment.
I like this here! I was going to propose that we move to the __init__ method -- and maybe that should also have it? -- but it's needed here I think, because the if/else below would take the first one available and could quietly be confusing. This is a good explicit check here.
There was a problem hiding this comment.
Here were some conclusions/points we arrived at during our discussion on 10/29:
Transformer.__init__():
- We should be able to instantiate a
Transformerwithout providing asource_fileandsource_dataset.- The only required params are
sourceandsource_record.
- The only required params are
- During instantiation,
source_fileandsource_datasetis only needed when callingself.get_run_data().
Transformer.get_run_data():
- Only required parameter is
source. source_fileandsource_datasetare still optional because the method falls back to default values. Namely:run_data = { "source": source, "run_date": current_datetime.strftime("%Y-%m-%d"), "run_type": "daily", "run_id": run_id or str(uuid.uuid4()), "run_timestamp": run_timestamp or current_datetime.isoformat(), }
- An error is only raised if both
source_fileandsource_datasetare provided- Why: Run data is either parsed from
source_fileOR configured forsource_dataset; cannot be both.
- Why: Run data is either parsed from
|
@jonavellecuerdo - not a hard requirement, but may want to consider rebasing against |
e8b700c to
accdf35
Compare
ghukill
left a comment
There was a problem hiding this comment.
Approved! Nice work, thanks for the good discussions.
ehanson8
left a comment
There was a problem hiding this comment.
Looks great, the discussion landed this update in a solid place!
Why these changes are being introduced: * At this time, this feature only supports reading and transforming source records from the TIMDEX dataset for a given data source. In the event a transformer is updated, we can execute the TIMDEX ETL pipeline at the transformation step and skip the extract (or harvest) step. How this addresses that need: * Add CLI option '-d / --dataset' * Make CLI options --input-file and --output-location optional * Validate CLI input options * Transformer can parse source records from TIMDEX dataset * Transformer.get_run_data can handle 'source_dataset' as input * XML and JSON Transformer classes define methods to deserialize source records from the TIMDEX dataset * Simplify method for getting run data * Clarify CLI option when dataset is used as input Side effects of this change: * timdex-pipeline-lambdas needs to generate a transform command that reads and transforms source records from the TIMDEX dataset Relevant ticket(s): * https://mitlibraries.atlassian.net/browse/TIMX-700
accdf35 to
f0cbc87
Compare

Purpose and background context
Transmogrifier can read and transform source records from the TIMDEX dataset for a given (data) source. As described in TIMX-700, the motivation for this feature was to enable transformation for a subset of TIMDEX records. This does not imply subsetting the harvested records during the "extract" (or harvest) step of the TIMDEX ETL pipeline. Given that source records are already stored in the TIMDEX dataset, rerunning the "extract" step isn't required either.
At this time, this feature only supports reading and transforming source records associated with all "current" records for a given TIMDEX source, as this is expected to be the most common scenario we will encounter. That is:
Changes are made to a metadata transformation (a
Transformerin Transmogrifier) -> For each TIMDEX source that relies on theTransformer, re-transform all "current" source recordsHow can a reviewer manually see the effects of these changes?
These tests were run using a local TIMDEX dataset on my machine (I used Rclone to sync a local dataset with the TIMDEX dataset in Dev at some point previously).
The following env vars were set on my machine:
Launch IPython shell and ran the following commands:
Which prints the output:
MITAardVarktransformer so that thecontent_typevalue is updated. For testing purposes, I'm appending "Cool" to each content type.To avoid clobbering my local TIMDEX dataset, I copied the contents of my local TIMDEX dataset to a new location:
/Users/jcuerdo/timdex/cooler-dataset/(Note: "cooler" prefix)Now, let's try to rerun the transformer using the source records for the 2,061 records for the 'gismit' source in the TIMDEX dataset. Open another terminal and run the command:
Which prints the output:
Includes new or updated dependencies?
YES: Added
freezegunas dev dependency (usinguv add --dev)Changes expectations for external applications?
YES:
What are the relevant tickets?
Code review