commit 10004a555eed501de1c5380caa059cfd0d874833 Author: Ash Garcia Date: Sat Aug 10 15:51:17 2024 -0700 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fb94ebf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +testdata/ \ No newline at end of file diff --git a/aliasparts.csv b/aliasparts.csv new file mode 100644 index 0000000..f3cd8ad --- /dev/null +++ b/aliasparts.csv @@ -0,0 +1,394 @@ +first,second +Balls,Bear +Fluffy,Flight +Fashionable,Handbag +Vengeful,Direction +Embarrass,Example +Night,Attention +Redundant,Bag +Invention,Invention +Modular,Wolf +Closet,Oyster +Pleasant,Pancake +Study,Sun +Ravioli,Result +Boats,Power +Feeling,Donkey +Watery,Water +Construct,Scarecrow +Penitent,Journey +Ripe,Brick +Basketball,Boys +Petite,Pickle +Delicious,Carriage +Jimmy,Jamaica +Lively,Army +Sulky,Visitor +Tricky,Time +Incompetent,Flight +Telephone,Tub +Necessary,Skirt +Penguin,Printer +Mad,Rear +Gamer,Breath +Teeny-Tiny,Toe +Attempt,Dinosaurs +Sudden,Money +Shoot,Side +Zesty,Horse +Annoying,Caption +Paprika,Bane +Team,Wisdom +Offend,Twists +Evening,Supreme +Inca,Kola +Omega,Society +Ladybug,Zoomies +Offbeat,Grass +Charlie,Vibrate +Cheese,Cannon +Bad,Timing +End,Pancake +Climax,Koalas +Solo,Gopher +Courageous,Curtain +Join,Battle +Disagreeable,Behavior +Use,Floor +False,Opinion +Chunky,Curtain +Somber,Bells +Painful,Lawyer +Round,Sphere +Frightening,Credit +Best,Grandfather +Axiom,Address +False,Oil +Soothing,Saxophone +Ugly,Lobby +Whimsical,Advertisement +Marvelous,Toes +Mysterious,Beast +Sulfur,Sphere +Attempt,Nut +Responsible,Floor +Looping,Jungle +Lethal,Geese +Legal,Legs +Mere,Egg +Stocking,Slave +General,Giraffe +Simon,Switch +Purple,Pancake +Nice,Distribution +Flesh,Toothbrush +Political,Argument +Sweet,Creator +Scatter,Boys +Collect,Glue +Salami,Society +Alcoholic,Goat +Swear,Police +Wear,Banana +Display,Smash +Volcano,Frank +Temple,Hippie +Obtain,Loss +Two,Cats +Nice,Spoon +Marvelous,Competition +Long,Lips +Ostentatious,Ocelot +Religion,Gun +Pedro,Pablo +Operation,Cobweb +Dad,Bambino +Cuba,Colombo +Pot,Island +Jungle,Jazz +Strip,Disappointment +Irritating,Idea +Psychotic,Frog +Fearful,Form +Vast,Error +Accurate,Mine +Fretful,Form +Nice,Name +Fascinated,Father +Consider,Hate +Help,Help +Climax,Outside +Ordinary,Thread +Enormous,Icicle +Dark,Development +Enigma,Emerald +Test,Time +Geese,Goose +Functional,Cemetery +Obscene,Thing +Prefer,Pie +Public,Jellyfish +Grumpy,Ghost +Palma,Yes +Society,Shelter +Tasteful,Turn +Accelerate,Breakfast +Blind,Jail +Scream,Connection +Zodiac,Zipper +Disturbed,Man +Orange,Matrix +Pain,Party +Mere,Monkey +Aggressive,Agreement +Name,Son +Propose,Shoes +Lewd,Cake +Hard-To-Find,Stream +Suspend,Sleep +Magic,Mike +Sound,Wave +Song,Credit +Numberless,Design +Play,Segment +Moldy,Maid +Omega,Oregano +Flimsy,Balls +Become,Bat +Neuron,Magenta +Hum,Balls +Shaggy,Destruction +Big,Soup +Noisy,Machine +Convert,Grandmother +Aspiring,Floor +Legs,Help +Alien,Agenda +Exceed,Crack +Nominate,Nut +Stupendous,Brain +Distant,Tomato +Bad,Bells +Stink,Side +Athletic,Snorlax +Economy,Expert +Dynamic,Design +David,Gilbert +Orange,Matrix +Pain,Party +Mere,Monkey +Aggressive,Agreement +Name,Son +Propose,Shoes +Lewd,Cake +Hard-To-Find,Stream +Suspend,Sleep +Magic,Mike +Sound,Wave +Song,Credit +Numberless,Design +Play,Segment +Moldy,Maid +Omega,Oregano +Flimsy,Balls +Become,Bat +Neuron,Magenta +Hum,Balls +Shaggy,Destruction +Big,Soup +Noisy,Machine +Convert,Grandmother +Aspiring,Floor +Legs,Help +Alien,Agenda +Exceed,Crack +Nominate,Nut +Stupendous,Brain +Distant,Tomato +Bad,Bells +Stink,Side +Athletic,Snorlax +Economy,Expert +Dynamic,Design +David,Gilbert +Teal,Pocket +Troubleshoot,Things +Voice,Offset +Paint,Everywhere +Wakeful,Train +Not,Puppy +Disapprove,Year +Uncle,Worm +Bored,Brother +Panoramic,Visitor +Feeble,Fruit +Fantastic,Shop +Swing,Swung +Angry,Father +Tremendous,Expansion +Try,Wing +Punishment,Achiever +Drayze,Money +Plague,Doctor +Calculator,Enter +Barrel,Replica +Wiggly,Boy +Complex,Crook +Mimosa,Monarch +Graffiti,Soul +Ghost,Flower +Slow,Sonic +Bite,Committee +Papa,Puma +Holistic,Tooth +Dramatic,Man +Grouchy,Grape +Juicy,Jazz +Spiky,Chimp +Expect,Pain +Cheese,Meteor +Two,Swords +Psychedelic,Use +Shiver,Sleet +Foresee,Foot +Sonic,Phantom +Pam,Can +Inadvisable,Machine +Game,Cloth +Smiling,Mother +Finger,Sized +Abhorrent,Aftermath +Pancake,Maiden +Sentient,JalapeƱo +Snapple,Sider +Employ,Ray +Agenda,Dandy +Punctual,Pennies +Child,Cannon +Jetstream,Sam +Mana,Vein +Smite,Sword +Diva,Dee +Silly,Savage +Genre,Damage +Concussion,Simulator +Chew,Beef +Certainly,Hi-Tech +Born,Loser +Prefers,Broccoli +Spooked,Broccoli +Devilish,Afterthought +Chaos,Dunk +Phobic,Babies +Hammer,Lunchroom +Demonic,Holds +Expo,Jinx +Empowered,Turtle +Amish,Shinobi +More,Thirteens +Disprove,Woman +Young,Thug +Angry,Crayon +Examine,Harmony +Vivacious,Underwear +Operate,Swing +Continue,Frolicking +Nutritious,Pot +Song,Lot +Love,Lift +Saucy,Style +Read,Rules +Steppable,Horror +Large,Tune +Trans,Rights +Flamenco,Hardstyle +Huge,Jazz +Tech,Alert +Maybe,Eleven +Inform,Death +Shapeshifting,Soul +Splashy,Splash +Dimo,Pandering +Snooping,Canine +Tech,Notations +Two,Words +Turnt,Tables +Micro,Mambo +Forgetful,Form +Emotion,Lotion +Nine,Ten +The,The +Sodium,Carbonate +Colorful,Chase +Weed,Steps +Very,Serious +Improved,Nut +Ooh,Ahh +Fasten,Spoon +Substandard,Senior +Dispose,Cigarette +Ghoughpteighbteau,Tchoghs +Sixty,Nine +Seventeen,Sixteenths +Cool,Ten +Soft,Pants +Stylish,Seagull +Foot,Prints +Bee,Sat +New,Fifteen +Sandwiched,Romance +Makita,Power +No,Fantastics? +Jellyfish,Jambalaya +None,Modifiers +Redacted,Redacted +Butter,Fly +Fray,Yeah +Naur,Non +Go-Go,Ghost +Not,Eleven +Disk,Doctor +Hop,Along +Last,Literature +Corrupt,File +Dazzlin,Darlin +Leaping,Lemons +Boys,Off +Egglord,Turds +Everybody's,Something +Chief,Poser +Get,Funked +Rhythm,Challenge +Easy,Breezy +Diabolic,Dingleberry +Tucan,Bounce +Quatre,Cinq +Vanity,Devil +Alice,Liddell +Friendly,Frog +Secret,Santa +Moonlight,Silver +Imperfect,Title +Chicken,Nugget +Shelf,Elf +Eccentric,Catastrophe +Beaver,Bunions +Dig,Drug +Elegant,Eggnog +Uncharacteristically,Sesquipedalian +Indecisive,Identity +Play,Melty +Tranquil,Rat +Hall,Days +Unexpected,Item +An,Chart +Snail,Spoon +Boss,Fight +Got,Spunned +Throat,Goat +Scientific,Icicle +Grateful,Knot +Sun,Goddess +Banana,Special +Sleigh,It +Impossible,Burst \ No newline at end of file diff --git a/anonymize-entries.py b/anonymize-entries.py new file mode 100644 index 0000000..cbcd662 --- /dev/null +++ b/anonymize-entries.py @@ -0,0 +1,177 @@ +import argparse +import csv +import _csv +import os +from random import Random +import sys +from tempfile import TemporaryDirectory +from typing import Optional +from zipfile import ZipFile + +from fs.zipfs import ZipFS +import simfile + + +#################### +# Script arguments # +#################### + + +class AnonymizeEntriesArgs: + """Stores the command-line arguments for this script.""" + + csv: str + files: str + dry_run: bool + + +def argparser(): + """Get an ArgumentParser instance for this command-line script.""" + parser = argparse.ArgumentParser() + parser.add_argument("csv", type=str, help="path to the CSV file of form responses") + parser.add_argument( + "files", type=str, help="path to the directory of file responses" + ) + parser.add_argument( + "-d", + "--dry-run", + action=argparse.BooleanOptionalAction, + help="preview changes without writing the file", + ) + return parser + + +CsvContents = list[dict[str, str]] + + +################ +# Script logic # +################ + + +def assert_valid_file_paths(args: AnonymizeEntriesArgs): + assert os.path.isfile(args.csv), f"{repr(args.csv)} is not a file" + assert os.path.isdir(args.files), f"{repr(args.files)} is not a directory" + + +def load_csv_contents(args: AnonymizeEntriesArgs): + with open(args.csv, "r") as csvfile: + return list(csv.DictReader(csvfile)) + + +def load_alias_parts(csvpath: str) -> tuple[list[str], list[str]]: + def extract_alias_parts(csv: "_csv._reader"): + return tuple(zip(*((line[0], line[1]) for n, line in enumerate(csv) if n > 0))) + + with open(csvpath, "r") as csvfile: + alias_parts = extract_alias_parts(csv.reader(csvfile)) + + print(f"Loaded {sum(len(part) for part in alias_parts)} alias parts") + + return alias_parts + + +def assert_known_google_forms_columns_present(csv_contents: CsvContents): + assert ( + "Timestamp" in csv_contents[0] + ), 'Provided CSV file does not have a "Timestamp" column' + assert ( + "Email Address" in csv_contents[0] + ), 'Provided CSV file does not have an "Email Address" column' + + +def detect_filename_column(csv_contents: CsvContents) -> str: + maybe_filename_columns = [ + column for (column, value) in csv_contents[0].items() if value.endswith(".zip") + ] + assert ( + len(maybe_filename_columns) != 0 + ), 'First data row of provided CSV file has no cell ending in ".zip"' + assert ( + len(maybe_filename_columns) == 1 + ), 'First data row of provided CSV file has multiple cells ending in ".zip"' + filename_column = maybe_filename_columns[0] + print(f"Detected filename column: {repr(filename_column)}") + return filename_column + + +def maybe_generate_and_persist_aliases( + args: AnonymizeEntriesArgs, + alias_parts: tuple[list[str], list[str]], + csv_contents: CsvContents, +): + reuse_aliases = "Generated Alias" in csv_contents[0] + + if reuse_aliases: + print("Reusing generated aliases") + else: + for row in csv_contents: + random = Random("; ".join([row["Email Address"], args.csv, args.files])) + row["Generated Alias"] = ( + f"{random.choice(alias_parts[0])} {random.choice(alias_parts[0])}" + ) + print("Generated an alias for each entry") + + if args.dry_run: + print("Dry run - not writing generated aliases back to CSV") + else: + with open(args.csv, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=csv_contents[0].keys()) + writer.writeheader() + for row in csv_contents: + writer.writerow(row) + print("Wrote generated aliases back to CSV") + + return 0 + + +def maybe_remove_and_persist_resubmitted_entries(csv_contents: CsvContents): + print("STUB - maybe_remove_and_persist_resubmitted_entries") + + +def extract_entries_to_temporary_folder( + args: AnonymizeEntriesArgs, csv_contents: CsvContents +): + print("STUB - extract_entries_to_temporary_folder") + + +def anonymize_entries( + args: AnonymizeEntriesArgs, + csv_contents: CsvContents, + temp_dir: Optional[TemporaryDirectory], +): + print("STUB - anonymize_entries") + + +def zip_anonymized_entries( + args: AnonymizeEntriesArgs, + csv_contents: CsvContents, + temp_dir: Optional[TemporaryDirectory], +): + print("STUB - zip_anonymized_entries") + + +############### +# Main method # +############### + + +def main(argv: list[str]): + args = argparser().parse_args(argv[1:], namespace=AnonymizeEntriesArgs()) + assert_valid_file_paths(args) + alias_parts = load_alias_parts("aliasparts.csv") + csv_contents = load_csv_contents(args) + assert_known_google_forms_columns_present(csv_contents) + filename_column = detect_filename_column(csv_contents) + maybe_generate_and_persist_aliases(args, alias_parts, csv_contents) + + # Everything above is implemented; everything below is a stub + + maybe_remove_and_persist_resubmitted_entries(csv_contents) + temp_folder = extract_entries_to_temporary_folder(args, csv_contents) + anonymize_entries(args, csv_contents, temp_folder) + zip_anonymized_entries(args, csv_contents, temp_folder) + + +if __name__ == "__main__": + main(sys.argv) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..e3fa936 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,207 @@ +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = "*" +files = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] + +[[package]] +name = "black" +version = "24.8.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:09cdeb74d494ec023ded657f7092ba518e8cf78fa8386155e4a03fdcc44679e6"}, + {file = "black-24.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:81c6742da39f33b08e791da38410f32e27d632260e599df7245cccee2064afeb"}, + {file = "black-24.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:707a1ca89221bc8a1a64fb5e15ef39cd755633daa672a9db7498d1c19de66a42"}, + {file = "black-24.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:d6417535d99c37cee4091a2f24eb2b6d5ec42b144d50f1f2e436d9fe1916fe1a"}, + {file = "black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1"}, + {file = "black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af"}, + {file = "black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4"}, + {file = "black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af"}, + {file = "black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368"}, + {file = "black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed"}, + {file = "black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018"}, + {file = "black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2"}, + {file = "black-24.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:505289f17ceda596658ae81b61ebbe2d9b25aa78067035184ed0a9d855d18afd"}, + {file = "black-24.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b19c9ad992c7883ad84c9b22aaa73562a16b819c1d8db7a1a1a49fb7ec13c7d2"}, + {file = "black-24.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f13f7f386f86f8121d76599114bb8c17b69d962137fc70efe56137727c7047e"}, + {file = "black-24.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:f490dbd59680d809ca31efdae20e634f3fae27fba3ce0ba3208333b713bc3920"}, + {file = "black-24.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eab4dd44ce80dea27dc69db40dab62d4ca96112f87996bca68cd75639aeb2e4c"}, + {file = "black-24.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3c4285573d4897a7610054af5a890bde7c65cb466040c5f0c8b732812d7f0e5e"}, + {file = "black-24.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e84e33b37be070ba135176c123ae52a51f82306def9f7d063ee302ecab2cf47"}, + {file = "black-24.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:73bbf84ed136e45d451a260c6b73ed674652f90a2b3211d6a35e78054563a9bb"}, + {file = "black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed"}, + {file = "black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "fs" +version = "2.4.16" +description = "Python's filesystem abstraction layer" +optional = false +python-versions = "*" +files = [ + {file = "fs-2.4.16-py2.py3-none-any.whl", hash = "sha256:660064febbccda264ae0b6bace80a8d1be9e089e0a5eb2427b7d517f9a91545c"}, + {file = "fs-2.4.16.tar.gz", hash = "sha256:ae97c7d51213f4b70b6a958292530289090de3a7e15841e108fbe144f069d313"}, +] + +[package.dependencies] +appdirs = ">=1.4.3,<1.5.0" +setuptools = "*" +six = ">=1.10,<2.0" + +[package.extras] +scandir = ["scandir (>=1.5,<2.0)"] + +[[package]] +name = "msdparser" +version = "2.0.0" +description = "Robust & lightning fast MSD parser (StepMania file format)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "msdparser-2.0.0-py3-none-any.whl", hash = "sha256:bbef8c89b2dc16183ba714354b093fff11a43469971c42972284c5046826002d"}, + {file = "msdparser-2.0.0.tar.gz", hash = "sha256:9bb6cf4b705c76850b1ce22823c768b62ba2bb63d1c2407bbe3e2c23e38be8e3"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "setuptools" +version = "72.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, + {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, +] + +[package.extras] +core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "simfile" +version = "2.1.1" +description = "Modern simfile library for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "simfile-2.1.1-py3-none-any.whl", hash = "sha256:133094a33cf6b841ca7e620880ef8354c56dd4aef6b2c77582de7f120f69c620"}, + {file = "simfile-2.1.1.tar.gz", hash = "sha256:a7885f8395cdd0f19cd79da34e5675da778aa0e8dda76543cf075e21b862c4b7"}, +] + +[package.dependencies] +fs = ">=2.4.15,<2.5.0" +msdparser = ">=2.0.0,<2.1.0" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.11" +content-hash = "130aa8886dcc190a88f8094e7e74aefbddad10ce60ab3d63d74c126bf6b5cc04" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c23d92e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,17 @@ +[tool.poetry] +name = "dimocracy-voucher" +version = "0.1.0" +description = "" +authors = ["ashastral "] +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.11" +simfile = "^2.1.1" + +[tool.poetry.group.dev.dependencies] +black = "^24.8.0" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"