From b47d3c23112878cd13d654f63ae1c12fc9014e0a Mon Sep 17 00:00:00 2001 From: HelenZhutt Date: Mon, 3 Nov 2025 14:42:39 -0500 Subject: [PATCH 01/13] initialize the repo and add two functions --- pyflirt/__init__.py | 2 ++ pyflirt/api.py | 76 +++++++++++++++++++++++++++++++++++++++++++ pyflirt/data.py | 30 +++++++++++++++++ pyproject.toml | 13 ++++++++ tests/test_pyflirt.py | 19 +++++++++++ 5 files changed, 140 insertions(+) create mode 100644 pyflirt/__init__.py create mode 100644 pyflirt/api.py create mode 100644 pyflirt/data.py create mode 100644 pyproject.toml create mode 100644 tests/test_pyflirt.py diff --git a/pyflirt/__init__.py b/pyflirt/__init__.py new file mode 100644 index 0000000..ebb0266 --- /dev/null +++ b/pyflirt/__init__.py @@ -0,0 +1,2 @@ +from .api import line, lines, categories +__all__ = ["line", "lines", "categories"] diff --git a/pyflirt/api.py b/pyflirt/api.py new file mode 100644 index 0000000..ea54c1d --- /dev/null +++ b/pyflirt/api.py @@ -0,0 +1,76 @@ +import random +from typing import List, Optional +from .data import BANK, categories as _categories + +__all__ = ["line", "lines", "categories"] + +def categories() -> List[str]: + return _categories() + +def _check_cat(cat: Optional[str]) -> Optional[str]: + if cat is None: + return None + if cat not in BANK: + valid = ", ".join(_categories()) + raise ValueError(f"Unknown category {cat!r}. Choose from {valid}.") + return cat + +def _pool(cat: Optional[str], cheese: int) -> List[dict]: + """Return candidate lines filtered by category and cheese. + If filtering wipes everything out, fall back to the unfiltered pool. + """ + def ok(e: dict) -> bool: + return e.get("cheese", 3) <= cheese + + if cat is None: + picks = [e for items in BANK.values() for e in items if ok(e)] + else: + picks = [e for e in BANK[cat] if ok(e)] + + if not picks: + picks = BANK[cat] if cat else [e for items in BANK.values() for e in items] + return picks + +def _with_name(text: str, name: Optional[str]) -> str: + if "{name}" in text: + return text.replace("{name}", name or "you") + return text + +def line( + category: Optional[str] = "nerdy", + name: Optional[str] = None, + cheese: int = 2, + seed: Optional[int] = None, +) -> str: + if not (1 <= int(cheese) <= 5): + raise ValueError("cheese must be in 1..5") + category = _check_cat(category) + rng = random.Random(seed) + choice = rng.choice(_pool(category, cheese)) + return _with_name(choice["text"], name) + +def lines( + n: int = 5, + category: Optional[str] = None, + name: Optional[str] = None, + cheese: int = 2, + seed: Optional[int] = None, +) -> List[str]: + if n <= 0: + return [] + if not (1 <= int(cheese) <= 5): + raise ValueError("cheese must be in 1..5") + category = _check_cat(category) + rng = random.Random(seed) + pool = _pool(category, cheese) + + out: List[str] = [] + if len(pool) >= n: + for e in rng.sample(pool, n): + out.append(_with_name(e["text"], name)) + return out + + for _ in range(n): + e = rng.choice(pool) + out.append(_with_name(e["text"], name)) + return out diff --git a/pyflirt/data.py b/pyflirt/data.py new file mode 100644 index 0000000..a24b92a --- /dev/null +++ b/pyflirt/data.py @@ -0,0 +1,30 @@ +# pyflirt/data.py +BANK = { + "nerdy": [ + {"text": "Are you made of copper and tellurium? Because you’re Cu-Te.", "cheese": 2}, + {"text": "Are you a quantum tunnel? Because you went straight through my barriers.", "cheese": 3}, + {"text": "Is your name Wi-Fi? Because I feel a strong connection.", "cheese": 3}, + {"text": "Are you a neural net, {name}? Because I keep overfitting to you.", "cheese": 4}, + ], + "poetic": [ + {"text": "{name}, shall I compare thee to a stable release? Thou art rarer and far more dependable.", "cheese": 4}, + {"text": "I’d cross the version gulf for thee, and tag a release upon thy smile.", "cheese": 3}, + ], + "cs": [ + {"text": "If love were a bug, I’d still refuse to close your ticket.", "cheese": 1}, + {"text": "Do you believe in love at first compile, {name}, or should I re-run?", "cheese": 2}, + {"text": "You must be Git—my heart commits to you.", "cheese": 2}, + ], + "math": [ + {"text": "You must be my limit—I’m approaching you from every direction.", "cheese": 4}, + {"text": "If we were vectors, we’d be perfectly aligned.", "cheese": 2}, + {"text": "Are you √-1? You’re unreal—and I can’t stop imagining us.", "cheese": 4}, + {"text": "We are coprime; the only common divisor is one heart.", "cheese": 3}, + ], + "classic": [ + {"text": "Are you a magician? Because whenever I look at you, everyone else disappears.", "cheese": 2}, + ], +} + +def categories(): + return sorted(BANK.keys()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..93599c0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +# pyproject.toml +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyflirt" +version = "0.1.0" +description = "Playful, seedable pickup lines for the terminal." +readme = "README.md" +requires-python = ">=3.9" +authors = [{ name = "Team Quartz" }] +dependencies = [] \ No newline at end of file diff --git a/tests/test_pyflirt.py b/tests/test_pyflirt.py new file mode 100644 index 0000000..e397366 --- /dev/null +++ b/tests/test_pyflirt.py @@ -0,0 +1,19 @@ +from pyflirt import line, lines, categories + +def test_categories_present(): + assert "nerdy" in categories() + +def test_line_seed_stable(): + assert line(category="nerdy", seed=42) == line(category="nerdy", seed=42) + +def test_lines_len_and_content(): + arr = lines(n=3, name="Sam", seed=123) + assert len(arr) == 3 + assert all(isinstance(s, str) and s for s in arr) + +def test_cheese_bounds(): + import pytest + with pytest.raises(ValueError): + line(cheese=0) + with pytest.raises(ValueError): + lines(n=2, cheese=6) From 2e48b360c7ac7b577ce1dfee452275ca660e1c40 Mon Sep 17 00:00:00 2001 From: danielleesignup Date: Mon, 3 Nov 2025 15:28:10 -0500 Subject: [PATCH 02/13] Set up project dirs and wrote compliment function --- .github/workflows/ci.yml | 55 ++++++ Pipfile | 15 ++ Pipfile.lock | 392 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 41 ++++ src/pyflirt/__init__.py | 20 ++ src/pyflirt/core.py | 118 ++++++++++++ src/pyflirt/main.py | 8 + tests/test_compliment.py | 35 ++++ 8 files changed, 684 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 pyproject.toml create mode 100644 src/pyflirt/__init__.py create mode 100644 src/pyflirt/core.py create mode 100644 src/pyflirt/main.py create mode 100644 tests/test_compliment.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ef9940c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: CI + +on: + pull_request: + branches: [ pipfile-experiment ] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-and-build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.12"] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Upgrade pip and install build tools + run: | + python -m pip install --upgrade pip + python -m pip install build pytest pipenv + + - name: Install dependencies with Pipenv + run: | + pipenv install --dev --ignore-pipfile + pipenv run pip install -e . + + - name: Run tests + run: | + pipenv run pytest -q + + - name: Build package (sdist & wheel) + run: | + pipenv run python -m build + + - name: Upload dist artifacts + uses: actions/upload-artifact@v4 + with: + name: pyflirt-dist-${{ matrix.python-version }} + path: dist/* diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..b80fa08 --- /dev/null +++ b/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] + +[dev-packages] +pytest = "*" +build = "*" +twine = "*" + +[requires] +python_version = "3.12" +python_full_version = "3.12.4" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..6c4d705 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,392 @@ +{ + "_meta": { + "hash": { + "sha256": "04147162fb671d30f16143546df2d8184d009fca6fe12b1f9a5362c5ce374bdf" + }, + "pipfile-spec": 6, + "requires": { + "python_full_version": "3.12.4", + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": {}, + "develop": { + "build": { + "hashes": [ + "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", + "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.3.0" + }, + "certifi": { + "hashes": [ + "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", + "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" + ], + "markers": "python_version >= '3.7'", + "version": "==2025.10.5" + }, + "charset-normalizer": { + "hashes": [ + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.4" + }, + "colorama": { + "hashes": [ + "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", + "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", + "version": "==0.4.6" + }, + "docutils": { + "hashes": [ + "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d", + "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8" + ], + "markers": "python_version >= '3.9'", + "version": "==0.22.2" + }, + "id": { + "hashes": [ + "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d", + "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "idna": { + "hashes": [ + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" + ], + "markers": "python_version >= '3.8'", + "version": "==3.11" + }, + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, + "jaraco.classes": { + "hashes": [ + "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", + "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "jaraco.context": { + "hashes": [ + "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", + "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.1" + }, + "jaraco.functools": { + "hashes": [ + "sha256:227ff8ed6f7b8f62c56deff101545fa7543cf2c8e7b82a7c2116e672f29c26e8", + "sha256:cfd13ad0dd2c47a3600b439ef72d8615d482cedcff1632930d6f28924d92f294" + ], + "markers": "python_version >= '3.9'", + "version": "==4.3.0" + }, + "keyring": { + "hashes": [ + "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", + "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd" + ], + "markers": "python_version >= '3.9'", + "version": "==25.6.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", + "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3" + ], + "markers": "python_version >= '3.10'", + "version": "==4.0.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", + "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd" + ], + "markers": "python_version >= '3.9'", + "version": "==10.8.0" + }, + "nh3": { + "hashes": [ + "sha256:019ecbd007536b67fdf76fab411b648fb64e2257ca3262ec80c3425c24028c80", + "sha256:03d617e5c8aa7331bd2659c654e021caf9bba704b109e7b2b28b039a00949fe5", + "sha256:0dca4365db62b2d71ff1620ee4f800c4729849906c5dd504ee1a7b2389558e31", + "sha256:0fe7ee035dd7b2290715baf29cb27167dddd2ff70ea7d052c958dbd80d323c99", + "sha256:13398e676a14d6233f372c75f52d5ae74f98210172991f7a3142a736bd92b131", + "sha256:169db03df90da63286e0560ea0efa9b6f3b59844a9735514a1d47e6bb2c8c61b", + "sha256:1710f3901cd6440ca92494ba2eb6dc260f829fa8d9196b659fa10de825610ce0", + "sha256:1f9ba555a797dbdcd844b89523f29cdc90973d8bd2e836ea6b962cf567cadd93", + "sha256:2ab70e8c6c7d2ce953d2a58102eefa90c2d0a5ed7aa40c7e29a487bc5e613131", + "sha256:2c9850041b77a9147d6bbd6dbbf13eeec7009eb60b44e83f07fcb2910075bf9b", + "sha256:403c11563e50b915d0efdb622866d1d9e4506bce590ef7da57789bf71dd148b5", + "sha256:45c953e57028c31d473d6b648552d9cab1efe20a42ad139d78e11d8f42a36130", + "sha256:562da3dca7a17f9077593214a9781a94b8d76de4f158f8c895e62f09573945fe", + "sha256:6d66f41672eb4060cf87c037f760bdbc6847852ca9ef8e9c5a5da18f090abf87", + "sha256:7064ccf5ace75825bd7bf57859daaaf16ed28660c1c6b306b649a9eda4b54b1e", + "sha256:72d67c25a84579f4a432c065e8b4274e53b7cf1df8f792cf846abfe2c3090866", + "sha256:7bb18403f02b655a1bbe4e3a4696c2ae1d6ae8f5991f7cacb684b1ae27e6c9f7", + "sha256:91e9b001101fb4500a2aafe3e7c92928d85242d38bf5ac0aba0b7480da0a4cd6", + "sha256:a40202fd58e49129764f025bbaae77028e420f1d5b3c8e6f6fd3a6490d513868", + "sha256:c8745454cdd28bbbc90861b80a0111a195b0e3961b9fa2e672be89eb199fa5d8", + "sha256:cf5964d54edd405e68583114a7cba929468bcd7db5e676ae38ee954de1cfc104", + "sha256:d18957a90806d943d141cc5e4a0fefa1d77cf0d7a156878bf9a66eed52c9cc7d", + "sha256:dce4248edc427c9b79261f3e6e2b3ecbdd9b88c267012168b4a7b3fc6fd41d13", + "sha256:f2f55c4d2d5a207e74eefe4d828067bbb01300e06e2a7436142f915c5928de07", + "sha256:f394759a06df8b685a4ebfb1874fb67a9cbfd58c64fc5ed587a663c0e63ec376", + "sha256:f97f8b25cb2681d25e2338148159447e4d689aafdccfcf19e61ff7db3905768a" + ], + "markers": "python_version >= '3.8'", + "version": "==0.3.2" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pyproject-hooks": { + "hashes": [ + "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", + "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "pytest": { + "hashes": [ + "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", + "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==8.4.2" + }, + "pywin32-ctypes": { + "hashes": [ + "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", + "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755" + ], + "markers": "python_version >= '3.6'", + "version": "==0.2.3" + }, + "readme-renderer": { + "hashes": [ + "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" + ], + "markers": "python_version >= '3.9'", + "version": "==44.0" + }, + "requests": { + "hashes": [ + "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", + "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf" + ], + "markers": "python_version >= '3.9'", + "version": "==2.32.5" + }, + "requests-toolbelt": { + "hashes": [ + "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", + "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.0.0" + }, + "rfc3986": { + "hashes": [ + "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", + "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" + }, + "rich": { + "hashes": [ + "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", + "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==14.2.0" + }, + "twine": { + "hashes": [ + "sha256:418ebf08ccda9a8caaebe414433b0ba5e25eb5e4a927667122fbe8f829f985d8", + "sha256:e5ed0d2fd70c9959770dce51c8f39c8945c574e18173a7b81802dab51b4b75cf" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==6.2.0" + }, + "urllib3": { + "hashes": [ + "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", + "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" + ], + "markers": "python_version >= '3.9'", + "version": "==2.5.0" + } + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..56ead1f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,41 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyflirt" +description = "A lighthearted Python package for developer-themed pickup lines." +version = "0.1.0" +authors = [ + { name = "Daniel", email = "dl4458@nyu.edu" }, +] +license = { file = "LICENSE" } +readme = "README.md" +keywords = ["fun", "pickup lines", "developer humor"] +requires-python = ">=3.9" +classifiers = [ + "Programming Language :: Python :: 3", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + +dependencies = [] + +[project.optional-dependencies] +dev = ["pytest"] + +[project.urls] +"Homepage" = "https://github.com/swe-students-fall2025/3-python-package-team_quartz" +"Repository" = "https://github.com/swe-students-fall2025/3-python-package-team_quartz.git" +"Bug Tracker" = "https://github.com/swe-students-fall2025/3-python-package-team_quartz/issues" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +testpaths = ["tests"] +pythonpath = ["src"] diff --git a/src/pyflirt/__init__.py b/src/pyflirt/__init__.py new file mode 100644 index 0000000..84611f4 --- /dev/null +++ b/src/pyflirt/__init__.py @@ -0,0 +1,20 @@ +""" +pyflirt 💘 + +APIs: +- line(category, name, cheese, seed) +- lines(n, categories, name, cheese, seed) +- compliment(role, mood, name, emojis, seed) +- rate_line(text, metric, seed) +""" + +from .core import ( + compliment, +) + +__all__ = [ + "compliment", + +] + +__version__ = "0.1.0" diff --git a/src/pyflirt/core.py b/src/pyflirt/core.py new file mode 100644 index 0000000..a25b874 --- /dev/null +++ b/src/pyflirt/core.py @@ -0,0 +1,118 @@ +""" +core.py — core logic for pyflirt 💘 + +Currently includes: +- compliment(): generate a developer-themed compliment +""" + +import random + + +__all__ = ["compliment"] + + +def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): + """ + Generate a developer-themed compliment. + + Args: + role (str): One of ["developer", "designer", "manager", "data"]. + mood (str): One of ["sweet", "cheeky", "nerdy"]. + name (str, optional): Person's name to personalize. + emojis (int): Number of 💖 emojis to append. + seed (int, optional): Random seed for deterministic selection. + + Returns: + str: A playful compliment string. + """ + rng = random.Random(seed) if seed is not None else random + + templates = { + "developer": { + "sweet": [ + "Your code is cleaner than a freshly cloned repo{name_bit}", + "You commit kindness with every push{name_bit}", + "You’re the pull request everyone approves instantly{name_bit}", + ], + "cheeky": [ + "You refactor hearts, not just code{name_bit}", + "You’ve got more charm than a recursive function{name_bit}", + "You must be a keyboard shortcut—because you’re my type{name_bit}", + ], + "nerdy": [ + "You debug my sadness faster than VSCode{name_bit}", + "You’re the semicolon that completes my statement{name_bit}", + "If beauty were an algorithm, you’d be O(1){name_bit}", + ], + }, + "designer": { + "sweet": [ + "Your aesthetic sense brightens every UI{name_bit}", + "You bring color theory to my grayscale days{name_bit}", + "Pixels align themselves just to please you{name_bit}", + ], + "cheeky": [ + "You must be a vector—because you’ve got direction{name_bit}", + "Are you a grid system? Because my heart is well-aligned{name_bit}", + "You kerningly complete me{name_bit}", + ], + "nerdy": [ + "You optimize whitespace like a legend{name_bit}", + "Your Figma files are pure poetry{name_bit}", + "Even Helvetica blushes when you walk in{name_bit}", + ], + }, + "manager": { + "sweet": [ + "You lead with empathy{name_bit}", + "Your standups make Mondays bearable{name_bit}", + "You’re the reason meetings actually end early{name_bit}", + ], + "cheeky": [ + "You manage hearts better than timelines{name_bit}", + "You’re my favorite deliverable{name_bit}", + "You’ve got more charisma than a sprint demo{name_bit}", + ], + "nerdy": [ + "You allocate my attention like a well-balanced backlog{name_bit}", + "KPIs envy your energy{name_bit}", + "Your OKRs? Outrageously Kind & Radiant{name_bit}", + ], + }, + "data": { + "sweet": [ + "You turn noise into beauty{name_bit}", + "Every dataset wishes it were as clean as your heart{name_bit}", + "You make outliers feel included{name_bit}", + ], + "cheeky": [ + "You must be a correlation—because you complete my regression{name_bit}", + "You’re my favorite variable{name_bit}", + "You pivot-table my emotions{name_bit}", + ], + "nerdy": [ + "Your confidence interval? 100%{name_bit}", + "You’re statistically significant in my life{name_bit}", + "Your curves fit any model{name_bit}", + ], + }, + } + + # Normalize and validate + role = role.lower() + mood = mood.lower() + if role not in templates: + raise ValueError(f"Unknown role '{role}'. Choose from {list(templates.keys())}.") + if mood not in templates[role]: + raise ValueError(f"Unknown mood '{mood}'. Choose from {list(templates[role].keys())}.") + + # Select random compliment + template = rng.choice(templates[role][mood]) + name_bit = f", {name}" if name else "" + compliment_text = template.format(name_bit=name_bit) + + # Add emojis if requested + if emojis > 0: + compliment_text += " " + "💖" * emojis + + return compliment_text diff --git a/src/pyflirt/main.py b/src/pyflirt/main.py new file mode 100644 index 0000000..42a2ffe --- /dev/null +++ b/src/pyflirt/main.py @@ -0,0 +1,8 @@ +from .core import compliment + +def main(): + print("💘 Welcome to pyflirt!") + print(compliment(role="developer", mood="cheeky", name="Alex", emojis=2)) + +if __name__ == "__main__": + main() diff --git a/tests/test_compliment.py b/tests/test_compliment.py new file mode 100644 index 0000000..93f7c30 --- /dev/null +++ b/tests/test_compliment.py @@ -0,0 +1,35 @@ +import pytest +from pyflirt import compliment + + +def test_compliment_returns_string(): + """Should always return a non-empty string.""" + out = compliment() + assert isinstance(out, str) + assert len(out) > 0 + + +def test_compliment_includes_name(): + """Name should appear in the compliment when provided.""" + result = compliment(role="developer", mood="nerdy", name="Alex", seed=1) + assert "Alex" in result + + +def test_compliment_emojis_count(): + """Correct number of emojis should be appended.""" + result = compliment(role="data", emojis=3, seed=2) + assert result.endswith("💖💖💖") + + +def test_compliment_is_deterministic_with_seed(): + """Using the same seed should produce the same output.""" + a = compliment(role="designer", mood="cheeky", seed=42) + b = compliment(role="designer", mood="cheeky", seed=42) + assert a == b + + +def test_compliment_differs_with_different_seeds(): + """Different seeds should generally give different compliments.""" + a = compliment(role="designer", mood="cheeky", seed=42) + b = compliment(role="designer", mood="cheeky", seed=43) + assert a != b From 3d15746685ab137f6c48071781a8abeaf8e5274b Mon Sep 17 00:00:00 2001 From: danielleesignup Date: Mon, 3 Nov 2025 16:06:28 -0500 Subject: [PATCH 03/13] Integrated compliment function to existing file structure --- pyflirt/__init__.py | 3 +- pyflirt/data.py | 30 -------- src/pyflirt/__init__.py | 5 +- {pyflirt => src/pyflirt}/api.py | 32 +++++++-- src/pyflirt/core.py | 118 -------------------------------- src/pyflirt/data.py | 102 +++++++++++++++++++++++++++ src/pyflirt/main.py | 8 --- tests/test_compliment.py | 35 ---------- tests/test_pyflirt.py | 35 ++++++++++ 9 files changed, 170 insertions(+), 198 deletions(-) delete mode 100644 pyflirt/data.py rename {pyflirt => src/pyflirt}/api.py (60%) delete mode 100644 src/pyflirt/core.py create mode 100644 src/pyflirt/data.py delete mode 100644 src/pyflirt/main.py delete mode 100644 tests/test_compliment.py diff --git a/pyflirt/__init__.py b/pyflirt/__init__.py index ebb0266..8b13789 100644 --- a/pyflirt/__init__.py +++ b/pyflirt/__init__.py @@ -1,2 +1 @@ -from .api import line, lines, categories -__all__ = ["line", "lines", "categories"] + diff --git a/pyflirt/data.py b/pyflirt/data.py deleted file mode 100644 index a24b92a..0000000 --- a/pyflirt/data.py +++ /dev/null @@ -1,30 +0,0 @@ -# pyflirt/data.py -BANK = { - "nerdy": [ - {"text": "Are you made of copper and tellurium? Because you’re Cu-Te.", "cheese": 2}, - {"text": "Are you a quantum tunnel? Because you went straight through my barriers.", "cheese": 3}, - {"text": "Is your name Wi-Fi? Because I feel a strong connection.", "cheese": 3}, - {"text": "Are you a neural net, {name}? Because I keep overfitting to you.", "cheese": 4}, - ], - "poetic": [ - {"text": "{name}, shall I compare thee to a stable release? Thou art rarer and far more dependable.", "cheese": 4}, - {"text": "I’d cross the version gulf for thee, and tag a release upon thy smile.", "cheese": 3}, - ], - "cs": [ - {"text": "If love were a bug, I’d still refuse to close your ticket.", "cheese": 1}, - {"text": "Do you believe in love at first compile, {name}, or should I re-run?", "cheese": 2}, - {"text": "You must be Git—my heart commits to you.", "cheese": 2}, - ], - "math": [ - {"text": "You must be my limit—I’m approaching you from every direction.", "cheese": 4}, - {"text": "If we were vectors, we’d be perfectly aligned.", "cheese": 2}, - {"text": "Are you √-1? You’re unreal—and I can’t stop imagining us.", "cheese": 4}, - {"text": "We are coprime; the only common divisor is one heart.", "cheese": 3}, - ], - "classic": [ - {"text": "Are you a magician? Because whenever I look at you, everyone else disappears.", "cheese": 2}, - ], -} - -def categories(): - return sorted(BANK.keys()) diff --git a/src/pyflirt/__init__.py b/src/pyflirt/__init__.py index 84611f4..31d78b3 100644 --- a/src/pyflirt/__init__.py +++ b/src/pyflirt/__init__.py @@ -7,6 +7,7 @@ - compliment(role, mood, name, emojis, seed) - rate_line(text, metric, seed) """ +from .api import line, lines, categories, compliment from .core import ( compliment, @@ -14,7 +15,9 @@ __all__ = [ "compliment", - + "line", + "lines", + "categories", ] __version__ = "0.1.0" diff --git a/pyflirt/api.py b/src/pyflirt/api.py similarity index 60% rename from pyflirt/api.py rename to src/pyflirt/api.py index ea54c1d..405fb61 100644 --- a/pyflirt/api.py +++ b/src/pyflirt/api.py @@ -1,10 +1,13 @@ import random from typing import List, Optional -from .data import BANK, categories as _categories +from .data import BANK, COMPLIMENT_TEMPLATES, categories as _categories +from typing import cast, Dict + __all__ = ["line", "lines", "categories"] def categories() -> List[str]: + """Return a sorted list of all available pickup line categories.""" return _categories() def _check_cat(cat: Optional[str]) -> Optional[str]: @@ -42,7 +45,8 @@ def line( cheese: int = 2, seed: Optional[int] = None, ) -> str: - if not (1 <= int(cheese) <= 5): + """Return one random pickup line for the given category, name, and cheese level.""" + if not 1 <= int(cheese) <= 5: raise ValueError("cheese must be in 1..5") category = _check_cat(category) rng = random.Random(seed) @@ -56,9 +60,10 @@ def lines( cheese: int = 2, seed: Optional[int] = None, ) -> List[str]: + """Return a list of n pickup lines matching the given category and cheese level.""" if n <= 0: return [] - if not (1 <= int(cheese) <= 5): + if not 1 <= int(cheese) <= 5: raise ValueError("cheese must be in 1..5") category = _check_cat(category) rng = random.Random(seed) @@ -67,10 +72,29 @@ def lines( out: List[str] = [] if len(pool) >= n: for e in rng.sample(pool, n): - out.append(_with_name(e["text"], name)) + out.append(_with_name(e["text"], name)) # type: ignore[index] return out for _ in range(n): e = rng.choice(pool) out.append(_with_name(e["text"], name)) return out + +def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): + """Return a list of n pickup lines matching the given category and cheese level.""" + rng = random.Random(seed) if seed is not None else random + + role = role.lower() + mood = mood.lower() + if role not in COMPLIMENT_TEMPLATES: + raise ValueError(f"Unknown role '{role}'. Choose from {list(COMPLIMENT_TEMPLATES.keys())}.") + if mood not in COMPLIMENT_TEMPLATES[role]: + raise ValueError(f"Unknown mood '{mood}'. Choose from {list(COMPLIMENT_TEMPLATES[role].keys())}.") + + template = rng.choice(COMPLIMENT_TEMPLATES[role][mood]) + name_bit = f", {name}" if name else "" + text = template.format(name_bit=name_bit) + + if emojis > 0: + text += " " + "💖" * emojis + return text \ No newline at end of file diff --git a/src/pyflirt/core.py b/src/pyflirt/core.py deleted file mode 100644 index a25b874..0000000 --- a/src/pyflirt/core.py +++ /dev/null @@ -1,118 +0,0 @@ -""" -core.py — core logic for pyflirt 💘 - -Currently includes: -- compliment(): generate a developer-themed compliment -""" - -import random - - -__all__ = ["compliment"] - - -def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): - """ - Generate a developer-themed compliment. - - Args: - role (str): One of ["developer", "designer", "manager", "data"]. - mood (str): One of ["sweet", "cheeky", "nerdy"]. - name (str, optional): Person's name to personalize. - emojis (int): Number of 💖 emojis to append. - seed (int, optional): Random seed for deterministic selection. - - Returns: - str: A playful compliment string. - """ - rng = random.Random(seed) if seed is not None else random - - templates = { - "developer": { - "sweet": [ - "Your code is cleaner than a freshly cloned repo{name_bit}", - "You commit kindness with every push{name_bit}", - "You’re the pull request everyone approves instantly{name_bit}", - ], - "cheeky": [ - "You refactor hearts, not just code{name_bit}", - "You’ve got more charm than a recursive function{name_bit}", - "You must be a keyboard shortcut—because you’re my type{name_bit}", - ], - "nerdy": [ - "You debug my sadness faster than VSCode{name_bit}", - "You’re the semicolon that completes my statement{name_bit}", - "If beauty were an algorithm, you’d be O(1){name_bit}", - ], - }, - "designer": { - "sweet": [ - "Your aesthetic sense brightens every UI{name_bit}", - "You bring color theory to my grayscale days{name_bit}", - "Pixels align themselves just to please you{name_bit}", - ], - "cheeky": [ - "You must be a vector—because you’ve got direction{name_bit}", - "Are you a grid system? Because my heart is well-aligned{name_bit}", - "You kerningly complete me{name_bit}", - ], - "nerdy": [ - "You optimize whitespace like a legend{name_bit}", - "Your Figma files are pure poetry{name_bit}", - "Even Helvetica blushes when you walk in{name_bit}", - ], - }, - "manager": { - "sweet": [ - "You lead with empathy{name_bit}", - "Your standups make Mondays bearable{name_bit}", - "You’re the reason meetings actually end early{name_bit}", - ], - "cheeky": [ - "You manage hearts better than timelines{name_bit}", - "You’re my favorite deliverable{name_bit}", - "You’ve got more charisma than a sprint demo{name_bit}", - ], - "nerdy": [ - "You allocate my attention like a well-balanced backlog{name_bit}", - "KPIs envy your energy{name_bit}", - "Your OKRs? Outrageously Kind & Radiant{name_bit}", - ], - }, - "data": { - "sweet": [ - "You turn noise into beauty{name_bit}", - "Every dataset wishes it were as clean as your heart{name_bit}", - "You make outliers feel included{name_bit}", - ], - "cheeky": [ - "You must be a correlation—because you complete my regression{name_bit}", - "You’re my favorite variable{name_bit}", - "You pivot-table my emotions{name_bit}", - ], - "nerdy": [ - "Your confidence interval? 100%{name_bit}", - "You’re statistically significant in my life{name_bit}", - "Your curves fit any model{name_bit}", - ], - }, - } - - # Normalize and validate - role = role.lower() - mood = mood.lower() - if role not in templates: - raise ValueError(f"Unknown role '{role}'. Choose from {list(templates.keys())}.") - if mood not in templates[role]: - raise ValueError(f"Unknown mood '{mood}'. Choose from {list(templates[role].keys())}.") - - # Select random compliment - template = rng.choice(templates[role][mood]) - name_bit = f", {name}" if name else "" - compliment_text = template.format(name_bit=name_bit) - - # Add emojis if requested - if emojis > 0: - compliment_text += " " + "💖" * emojis - - return compliment_text diff --git a/src/pyflirt/data.py b/src/pyflirt/data.py new file mode 100644 index 0000000..0f85766 --- /dev/null +++ b/src/pyflirt/data.py @@ -0,0 +1,102 @@ +# pyflirt/data.py +BANK = { + "nerdy": [ + {"text": "Are you made of copper and tellurium? Because you’re Cu-Te.", "cheese": 2}, + {"text": "Are you a quantum tunnel? Because you went straight through my barriers.", "cheese": 3}, + {"text": "Is your name Wi-Fi? Because I feel a strong connection.", "cheese": 3}, + {"text": "Are you a neural net, {name}? Because I keep overfitting to you.", "cheese": 4}, + ], + "poetic": [ + {"text": "{name}, shall I compare thee to a stable release? Thou art rarer and far more dependable.", "cheese": 4}, + {"text": "I’d cross the version gulf for thee, and tag a release upon thy smile.", "cheese": 3}, + ], + "cs": [ + {"text": "If love were a bug, I’d still refuse to close your ticket.", "cheese": 1}, + {"text": "Do you believe in love at first compile, {name}, or should I re-run?", "cheese": 2}, + {"text": "You must be Git—my heart commits to you.", "cheese": 2}, + ], + "math": [ + {"text": "You must be my limit—I’m approaching you from every direction.", "cheese": 4}, + {"text": "If we were vectors, we’d be perfectly aligned.", "cheese": 2}, + {"text": "Are you √-1? You’re unreal—and I can’t stop imagining us.", "cheese": 4}, + {"text": "We are coprime; the only common divisor is one heart.", "cheese": 3}, + ], + "classic": [ + {"text": "Are you a magician? Because whenever I look at you, everyone else disappears.", "cheese": 2}, + ], +} + +COMPLIMENT_TEMPLATES = { + "developer": { + "sweet": [ + "Your code is cleaner than a freshly cloned repo{name_bit}", + "You commit kindness with every push{name_bit}", + "You’re the pull request everyone approves instantly{name_bit}", + ], + "cheeky": [ + "You refactor hearts, not just code{name_bit}", + "You’ve got more charm than a recursive function{name_bit}", + "You must be a keyboard shortcut—because you’re my type{name_bit}", + ], + "nerdy": [ + "You debug my sadness faster than VSCode{name_bit}", + "You’re the semicolon that completes my statement{name_bit}", + "If beauty were an algorithm, you’d be O(1){name_bit}", + ], + }, + "designer": { + "sweet": [ + "Your aesthetic sense brightens every UI{name_bit}", + "You bring color theory to my grayscale days{name_bit}", + "Pixels align themselves just to please you{name_bit}", + ], + "cheeky": [ + "You must be a vector—because you’ve got direction{name_bit}", + "Are you a grid system? Because my heart is well-aligned{name_bit}", + "You kerningly complete me{name_bit}", + ], + "nerdy": [ + "You optimize whitespace like a legend{name_bit}", + "Your Figma files are pure poetry{name_bit}", + "Even Helvetica blushes when you walk in{name_bit}", + ], + }, + "manager": { + "sweet": [ + "You lead with empathy{name_bit}", + "Your standups make Mondays bearable{name_bit}", + "You’re the reason meetings actually end early{name_bit}", + ], + "cheeky": [ + "You manage hearts better than timelines{name_bit}", + "You’re my favorite deliverable{name_bit}", + "You’ve got more charisma than a sprint demo{name_bit}", + ], + "nerdy": [ + "You allocate my attention like a well-balanced backlog{name_bit}", + "KPIs envy your energy{name_bit}", + "Your OKRs? Outrageously Kind & Radiant{name_bit}", + ], + }, + "data": { + "sweet": [ + "You turn noise into beauty{name_bit}", + "Every dataset wishes it were as clean as your heart{name_bit}", + "You make outliers feel included{name_bit}", + ], + "cheeky": [ + "You must be a correlation—because you complete my regression{name_bit}", + "You’re my favorite variable{name_bit}", + "You pivot-table my emotions{name_bit}", + ], + "nerdy": [ + "Your confidence interval? 100%{name_bit}", + "You’re statistically significant in my life{name_bit}", + "Your curves fit any model{name_bit}", + ], + }, +} + +def categories(): + """Return a sorted list of all available pickup line categories.""" + return sorted(BANK.keys()) diff --git a/src/pyflirt/main.py b/src/pyflirt/main.py deleted file mode 100644 index 42a2ffe..0000000 --- a/src/pyflirt/main.py +++ /dev/null @@ -1,8 +0,0 @@ -from .core import compliment - -def main(): - print("💘 Welcome to pyflirt!") - print(compliment(role="developer", mood="cheeky", name="Alex", emojis=2)) - -if __name__ == "__main__": - main() diff --git a/tests/test_compliment.py b/tests/test_compliment.py deleted file mode 100644 index 93f7c30..0000000 --- a/tests/test_compliment.py +++ /dev/null @@ -1,35 +0,0 @@ -import pytest -from pyflirt import compliment - - -def test_compliment_returns_string(): - """Should always return a non-empty string.""" - out = compliment() - assert isinstance(out, str) - assert len(out) > 0 - - -def test_compliment_includes_name(): - """Name should appear in the compliment when provided.""" - result = compliment(role="developer", mood="nerdy", name="Alex", seed=1) - assert "Alex" in result - - -def test_compliment_emojis_count(): - """Correct number of emojis should be appended.""" - result = compliment(role="data", emojis=3, seed=2) - assert result.endswith("💖💖💖") - - -def test_compliment_is_deterministic_with_seed(): - """Using the same seed should produce the same output.""" - a = compliment(role="designer", mood="cheeky", seed=42) - b = compliment(role="designer", mood="cheeky", seed=42) - assert a == b - - -def test_compliment_differs_with_different_seeds(): - """Different seeds should generally give different compliments.""" - a = compliment(role="designer", mood="cheeky", seed=42) - b = compliment(role="designer", mood="cheeky", seed=43) - assert a != b diff --git a/tests/test_pyflirt.py b/tests/test_pyflirt.py index e397366..cda6cf6 100644 --- a/tests/test_pyflirt.py +++ b/tests/test_pyflirt.py @@ -1,4 +1,7 @@ from pyflirt import line, lines, categories +import pytest +from pyflirt import compliment + def test_categories_present(): assert "nerdy" in categories() @@ -17,3 +20,35 @@ def test_cheese_bounds(): line(cheese=0) with pytest.raises(ValueError): lines(n=2, cheese=6) + +def test_compliment_returns_string(): + """Should always return a non-empty string.""" + out = compliment() + assert isinstance(out, str) + assert len(out) > 0 + + +def test_compliment_includes_name(): + """Name should appear in the compliment when provided.""" + result = compliment(role="developer", mood="nerdy", name="Alex", seed=1) + assert "Alex" in result + + +def test_compliment_emojis_count(): + """Correct number of emojis should be appended.""" + result = compliment(role="data", emojis=3, seed=2) + assert result.endswith("💖💖💖") + + +def test_compliment_is_deterministic_with_seed(): + """Using the same seed should produce the same output.""" + a = compliment(role="designer", mood="cheeky", seed=42) + b = compliment(role="designer", mood="cheeky", seed=42) + assert a == b + + +def test_compliment_differs_with_different_seeds(): + """Different seeds should generally give different compliments.""" + a = compliment(role="designer", mood="cheeky", seed=42) + b = compliment(role="designer", mood="cheeky", seed=43) + assert a != b From 7f2178c4a7f108322996fde36c373cf4806acba6 Mon Sep 17 00:00:00 2001 From: MATTHEW VIOLA Date: Mon, 3 Nov 2025 17:14:11 -0500 Subject: [PATCH 04/13] added search function and test --- src/pyflirt/api.py | 31 +++++++++++++++++++++++++++++-- tests/test_pyflirt.py | 8 +++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/pyflirt/api.py b/src/pyflirt/api.py index 405fb61..1ad8b47 100644 --- a/src/pyflirt/api.py +++ b/src/pyflirt/api.py @@ -4,7 +4,7 @@ from typing import cast, Dict -__all__ = ["line", "lines", "categories"] +__all__ = ["line", "lines", "categories", "search"] def categories() -> List[str]: """Return a sorted list of all available pickup line categories.""" @@ -97,4 +97,31 @@ def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): if emojis > 0: text += " " + "💖" * emojis - return text \ No newline at end of file + return text + +def search( + substring: str, + category: Optional[str] = None, + cheese: Optional[int] = None +) -> List[str]: + category = _check_cat(category) + + if cheese is not None and not (1 <= int(cheese) <= 5): + raise ValueError("cheese must be in 1..5") + + if category is None: + lines = [line for cat in BANK.values() for line in cat] + else: + lines = BANK[category] + + if cheese is not None: + lines = [line for line in lines if line.get("cheese", 3) == cheese] + + results = [] + for line in lines: + text = line["text"].lower() + # Case-Insensitive Match + if text.contains(substring.lower()): + results.append(line["text"]) + + return results diff --git a/tests/test_pyflirt.py b/tests/test_pyflirt.py index cda6cf6..fcc7055 100644 --- a/tests/test_pyflirt.py +++ b/tests/test_pyflirt.py @@ -1,4 +1,4 @@ -from pyflirt import line, lines, categories +from pyflirt import line, lines, categories, search import pytest from pyflirt import compliment @@ -52,3 +52,9 @@ def test_compliment_differs_with_different_seeds(): a = compliment(role="designer", mood="cheeky", seed=42) b = compliment(role="designer", mood="cheeky", seed=43) assert a != b + +def test_lines_search(): + results = search("Are") + for result in results: + result = result.lower() + assert result.contains("are") From d06baa81ae2e64cec81f927370421bab5ebe1dec Mon Sep 17 00:00:00 2001 From: SamMurshed Date: Mon, 3 Nov 2025 21:03:27 -0500 Subject: [PATCH 05/13] Units tests + rater --- src/pyflirt/__init__.py | 9 +++++---- src/pyflirt/api.py | 29 ++++++++++++++++++++++++++++- tests/test_pyflirt.py | 23 +++++++++++++++++++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/pyflirt/__init__.py b/src/pyflirt/__init__.py index 31d78b3..ab4b1d3 100644 --- a/src/pyflirt/__init__.py +++ b/src/pyflirt/__init__.py @@ -7,17 +7,18 @@ - compliment(role, mood, name, emojis, seed) - rate_line(text, metric, seed) """ -from .api import line, lines, categories, compliment +from .api import line, lines, categories, compliment, rate_line -from .core import ( - compliment, -) +#from .core import ( +# compliment, +#) __all__ = [ "compliment", "line", "lines", "categories", + "rate_line" ] __version__ = "0.1.0" diff --git a/src/pyflirt/api.py b/src/pyflirt/api.py index 405fb61..62318e4 100644 --- a/src/pyflirt/api.py +++ b/src/pyflirt/api.py @@ -97,4 +97,31 @@ def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): if emojis > 0: text += " " + "💖" * emojis - return text \ No newline at end of file + return text + +def rate_line(text: str, metric: str = "length", seed: Optional[int] = None) -> float: + """ + Rate a pickup line by various heuristics. + Args: + text: pickup line string. + metric: rating metric ('length', 'cheese_level', 'random'). + seed: random seed for repeatability. + Returns: + A float rating score. + Raises: + ValueError if metric unknown. + """ + if metric not in ("length", "cheese_level", "random"): + raise ValueError(f"Unknown metric {metric!r}") + + if metric == "length": + length = len(text) + return float(max(0, 100 - length)) + + if metric == "cheese_level": + cheesy_words = ["love", "heart", "cute", "sweet", "charm", "kiss"] + count = sum(word in text.lower() for word in cheesy_words) + return float(count) + + rng = random.Random(seed) + return rng.uniform(0, 10) \ No newline at end of file diff --git a/tests/test_pyflirt.py b/tests/test_pyflirt.py index cda6cf6..69bb827 100644 --- a/tests/test_pyflirt.py +++ b/tests/test_pyflirt.py @@ -1,6 +1,8 @@ from pyflirt import line, lines, categories import pytest from pyflirt import compliment +from pyflirt import rate_line + def test_categories_present(): @@ -52,3 +54,24 @@ def test_compliment_differs_with_different_seeds(): a = compliment(role="designer", mood="cheeky", seed=42) b = compliment(role="designer", mood="cheeky", seed=43) assert a != b + +def test_rate_line_length(): + short_line = "Hi!" + long_line = "This is a really long pickup line that could be annoying." + assert rate_line(short_line, metric="length") > rate_line(long_line, metric="length") + +def test_rate_line_cheese_level(): + cheesy = "You have a sweet heart." + bland = "Hello." + assert rate_line(cheesy, metric="cheese_level") > rate_line(bland, metric="cheese_level") + +def test_rate_line_random_seed(): + r1 = rate_line("Test", metric="random", seed=42) + r2 = rate_line("Test", metric="random", seed=42) + r3 = rate_line("Test", metric="random", seed=43) + assert r1 == r2 + assert r1 != r3 + +def test_rate_line_invalid_metric(): + with pytest.raises(ValueError): + rate_line("test", metric="unknown") From d5a19af2f02a4ac17dd048cccbab602602996d3f Mon Sep 17 00:00:00 2001 From: SamMurshed Date: Mon, 3 Nov 2025 21:07:04 -0500 Subject: [PATCH 06/13] update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef9940c..3a2a13a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.12"] + python-version: ["3.10", "3.11"] steps: - name: Checkout From f02479462756a690eb7491de09217450e30a2ab6 Mon Sep 17 00:00:00 2001 From: SamMurshed Date: Mon, 3 Nov 2025 21:22:05 -0500 Subject: [PATCH 07/13] Try 3 --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a2a13a..f9b5090 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,11 +25,14 @@ jobs: uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: pip + - name: Clean pipenv environment if exists + run: pipenv --rm || true + - name: Upgrade pip and install build tools run: | python -m pip install --upgrade pip @@ -37,8 +40,11 @@ jobs: - name: Install dependencies with Pipenv run: | - pipenv install --dev --ignore-pipfile + pipenv --python $(which python) install --dev --ignore-pipfile pipenv run pip install -e . + + - name: Install package editable + run: pipenv run pip install -e . - name: Run tests run: | From dddccaa795a68959b1b99c45df7697140b25f26f Mon Sep 17 00:00:00 2001 From: jjl9930 Date: Tue, 4 Nov 2025 19:27:06 -0500 Subject: [PATCH 08/13] Update api.py --- src/pyflirt/api.py | 51 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/src/pyflirt/api.py b/src/pyflirt/api.py index 405fb61..f3d11f6 100644 --- a/src/pyflirt/api.py +++ b/src/pyflirt/api.py @@ -2,6 +2,8 @@ from typing import List, Optional from .data import BANK, COMPLIMENT_TEMPLATES, categories as _categories from typing import cast, Dict +import os, textwrap +from typing import Literal __all__ = ["line", "lines", "categories"] @@ -97,4 +99,51 @@ def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): if emojis > 0: text += " " + "💖" * emojis - return text \ No newline at end of file + return text +def stylize( + text: str, + *, + width: int | None = None, + uppercase: bool = False, + color: Literal["auto", "none", "magenta", "cyan", "green"] = "auto", +) -> str: + """ + Post-process a string with wrapping, casing, and ANSI color. + - width: wrap to N columns (None = no wrap) + - uppercase: True to SHOUT + - color: 'auto' enables color only on TTY; or force 'magenta'/'cyan'/'green'/'none' + """ + s = text.upper() if uppercase else text + if width: + s = "\n".join(textwrap.wrap(s, width=width)) + + palette = {"magenta": "\033[95m", "cyan": "\033[96m", "green": "\033[92m"} + reset = "\033[0m" + + if color == "none": + return s + if color == "auto": + color = "magenta" if os.getenv("TERM") else "none" + if color in palette: + return f"{palette[color]}{s}{reset}" + return s +def say( + *, + category: Optional[str] = "nerdy", + name: Optional[str] = None, + cheese: int = 2, + seed: Optional[int] = None, + width: int | None = None, + uppercase: bool = False, + color: Literal["auto", "none", "magenta", "cyan", "green"] = "auto", + emojis: int = 0, +) -> str: + """ + Generate a line, optionally decorate it (wrap/case/color), print it, and return it. + """ + txt = line(category=category, name=name, cheese=cheese, seed=seed) + if emojis > 0: + txt += " " + "💘" * emojis + pretty = stylize(txt, width=width, uppercase=uppercase, color=color) + print(pretty) + return pretty From 4ce8d09a783390f6e8453579da06f7cb2cd56ac1 Mon Sep 17 00:00:00 2001 From: jjl9930 Date: Tue, 4 Nov 2025 20:36:31 -0500 Subject: [PATCH 09/13] Delete pyflirt directory --- pyflirt/__init__.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 pyflirt/__init__.py diff --git a/pyflirt/__init__.py b/pyflirt/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/pyflirt/__init__.py +++ /dev/null @@ -1 +0,0 @@ - From c144822600c231c8ea6b19059b85de37f94269e4 Mon Sep 17 00:00:00 2001 From: jjl9930 Date: Tue, 4 Nov 2025 20:36:51 -0500 Subject: [PATCH 10/13] Update __init__.py --- src/pyflirt/__init__.py | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/pyflirt/__init__.py b/src/pyflirt/__init__.py index 31d78b3..63454b1 100644 --- a/src/pyflirt/__init__.py +++ b/src/pyflirt/__init__.py @@ -1,23 +1,13 @@ +# src/pyflirt/__init__.py """ pyflirt 💘 - -APIs: +Public API: +- categories() - line(category, name, cheese, seed) -- lines(n, categories, name, cheese, seed) +- lines(n, category, name, cheese, seed) - compliment(role, mood, name, emojis, seed) -- rate_line(text, metric, seed) """ -from .api import line, lines, categories, compliment - -from .core import ( - compliment, -) - -__all__ = [ - "compliment", - "line", - "lines", - "categories", -] +from .api import categories, line, lines, compliment +__all__ = ["categories", "line", "lines", "compliment"] __version__ = "0.1.0" From 87fffc682ff50c2de8b97e4ad746d7a1a654b2d3 Mon Sep 17 00:00:00 2001 From: jjl9930 Date: Tue, 4 Nov 2025 20:37:49 -0500 Subject: [PATCH 11/13] Update test_pyflirt.py --- tests/test_pyflirt.py | 66 +++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/tests/test_pyflirt.py b/tests/test_pyflirt.py index cda6cf6..277f26b 100644 --- a/tests/test_pyflirt.py +++ b/tests/test_pyflirt.py @@ -1,54 +1,40 @@ -from pyflirt import line, lines, categories import pytest -from pyflirt import compliment +from pyflirt import categories, line, lines, compliment +def test_categories_sorted_and_nonempty(): + cats = categories() + assert cats == sorted(cats) + assert len(cats) > 0 -def test_categories_present(): +def test_categories_reflect_bank_contents(): + # If BANK has "nerdy" in data, ensure it's present assert "nerdy" in categories() -def test_line_seed_stable(): - assert line(category="nerdy", seed=42) == line(category="nerdy", seed=42) - -def test_lines_len_and_content(): - arr = lines(n=3, name="Sam", seed=123) - assert len(arr) == 3 - assert all(isinstance(s, str) and s for s in arr) - -def test_cheese_bounds(): - import pytest +def test_line_invalid_cheese_raises(): with pytest.raises(ValueError): line(cheese=0) with pytest.raises(ValueError): - lines(n=2, cheese=6) - -def test_compliment_returns_string(): - """Should always return a non-empty string.""" - out = compliment() - assert isinstance(out, str) - assert len(out) > 0 - - -def test_compliment_includes_name(): - """Name should appear in the compliment when provided.""" - result = compliment(role="developer", mood="nerdy", name="Alex", seed=1) - assert "Alex" in result + line(cheese=6) +def test_line_unknown_category_raises(): + with pytest.raises(ValueError): + line(category="not-a-cat") -def test_compliment_emojis_count(): - """Correct number of emojis should be appended.""" - result = compliment(role="data", emojis=3, seed=2) - assert result.endswith("💖💖💖") +def test_lines_n_zero_returns_empty(): + assert lines(n=0) == [] +def test_lines_invalid_cheese_raises(): + with pytest.raises(ValueError): + lines(cheese=99) -def test_compliment_is_deterministic_with_seed(): - """Using the same seed should produce the same output.""" - a = compliment(role="designer", mood="cheeky", seed=42) - b = compliment(role="designer", mood="cheeky", seed=42) - assert a == b +def test_compliment_invalid_role_raises(): + with pytest.raises(ValueError): + compliment(role="astronaut") +def test_compliment_invalid_mood_raises(): + with pytest.raises(ValueError): + compliment(role="developer", mood="salty") -def test_compliment_differs_with_different_seeds(): - """Different seeds should generally give different compliments.""" - a = compliment(role="designer", mood="cheeky", seed=42) - b = compliment(role="designer", mood="cheeky", seed=43) - assert a != b +def test_compliment_emojis_appended(): + base = compliment(role="developer", mood="sweet", emojis=3, seed=1) + assert base.endswith("💖💖💖") From c15ec594552e583dd88697131363b422ca39608c Mon Sep 17 00:00:00 2001 From: jjl9930 Date: Tue, 4 Nov 2025 20:43:46 -0500 Subject: [PATCH 12/13] Update ci.yml --- .github/workflows/ci.yml | 44 +++++++++------------------------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef9940c..91c67f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,52 +2,28 @@ name: CI on: pull_request: - branches: [ pipfile-experiment ] + branches: [ main ] workflow_dispatch: -permissions: - contents: read - -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true - jobs: test-and-build: runs-on: ubuntu-latest strategy: - fail-fast: false matrix: python-version: ["3.10", "3.12"] - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: pip - - - name: Upgrade pip and install build tools - run: | - python -m pip install --upgrade pip - python -m pip install build pytest pipenv - - - name: Install dependencies with Pipenv - run: | - pipenv install --dev --ignore-pipfile - pipenv run pip install -e . - + - name: Install pipenv + run: python -m pip install --upgrade pip pipenv + - name: Sync dependencies + run: pipenv sync --dev - name: Run tests - run: | - pipenv run pytest -q - - - name: Build package (sdist & wheel) - run: | - pipenv run python -m build - + run: pipenv run pytest -q + - name: Build (sdist & wheel) + run: pipenv run python -m build - name: Upload dist artifacts uses: actions/upload-artifact@v4 with: From b5341f9ec4ac3c0f8a4ad1dd6227df438037cf1a Mon Sep 17 00:00:00 2001 From: HelenZhutt Date: Wed, 5 Nov 2025 01:24:07 -0600 Subject: [PATCH 13/13] Add more functions, tests and the demo to run all the functions --- README.md | 129 +++++++++++++++++++++++++++++++++++++++- examples/demo.py | 32 ++++++++++ src/pyflirt/__init__.py | 23 +++---- src/pyflirt/api.py | 53 ++++++++++++++++- tests/test_pyflirt.py | 53 ++++++++++++++++- 5 files changed, 274 insertions(+), 16 deletions(-) create mode 100644 examples/demo.py diff --git a/README.md b/README.md index 6022e0e..dbbfa13 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,128 @@ -# Python Package Exercise +# pyflirt -An exercise to create a Python package, build it, test it, distribute it, and use it. See [instructions](./instructions.md) for details. +A Python package that gives you developer-themed pickup lines and compliments. Because coding should be fun. + +## What is this? + +This package has a collection of cheesy (but configurable) pickup lines and compliments for developers, designers, managers, and data scientists. You can get random lines, filter by category, adjust the cheesiness level, and customize compliments. + +## Installation + +```bash +pip install pyflirt +``` + +## Quick Start + +```python +from pyflirt import line, lines, compliment, categories + +# Get one random pickup line +print(line()) + +# Get a line in a specific category +print(line(category="nerdy")) + +# Get multiple lines +print(lines(n=5, category="cs")) + +# Get a compliment +print(compliment(role="developer", mood="sweet")) +``` + +## Functions + +### `line(category="nerdy", name=None, cheese=2, seed=None)` + +Returns one random pickup line. + +- `category`: Pick a category like "nerdy", "cs", "math", "poetic", or "classic". Default is "nerdy". +- `name`: If the line supports it, this name will be inserted. +- `cheese`: How cheesy should it be? 1 (least cheesy) to 5 (very cheesy). Default is 2. +- `seed`: Optional number for reproducible results. + +Example: +```python +line(category="cs", name="Alex", cheese=2) +``` + +### `lines(n=5, category=None, name=None, cheese=2, seed=None)` + +Returns a list of pickup lines. + +- `n`: How many lines you want. +- Other parameters work the same as `line()`. + +Example: +```python +lines(n=3, category="math", cheese=3) +``` + +### `compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None)` + +Returns a compliment for a specific role. + +- `role`: Choose from "developer", "designer", "manager", or "data". +- `mood`: "sweet", "cheeky", or "nerdy". +- `name`: Optional name to include in the compliment. +- `emojis`: Number of heart emojis to add (0-5). +- `seed`: Optional number for reproducible results. + +Example: +```python +compliment(role="designer", mood="cheeky", name="Sam", emojis=2) +``` + +### `categories()` + +Returns a list of all available pickup line categories. + +Example: +```python +print(categories()) +# ['classic', 'cs', 'math', 'nerdy', 'poetic'] +``` + +## Development Setup + +If you want to work on this package: + +1. Clone the repo: +```bash +git clone https://github.com/swe-students-fall2025/3-python-package-team_quartz.git +cd 3-python-package-team_quartz +``` + +2. Install pipenv (if you don't have it): +```bash +pip install pipenv +``` + +3. Install dependencies: +```bash +pipenv install --dev +``` + +4. Activate the virtual environment: +```bash +pipenv shell +``` + +5. Run tests: +```bash +pytest +``` + +6. Build the package: +```bash +python -m build +``` + +## Links + +- [PyPI Package](https://pypi.org/project/pyflirt/) +- [GitHub Repository](https://github.com/swe-students-fall2025/3-python-package-team_quartz) + +## Contributors + +See the [contributors page](https://github.com/swe-students-fall2025/3-python-package-team_quartz/graphs/contributors) for a list of everyone who worked on this project. diff --git a/examples/demo.py b/examples/demo.py new file mode 100644 index 0000000..10d45ac --- /dev/null +++ b/examples/demo.py @@ -0,0 +1,32 @@ +from pyflirt import line, lines, categories, compliment, search, stats + +def main(): + print("== categories() ==") + cats = categories() + print(cats) + + cat = "nerdy" if "nerdy" in cats else (cats[0] if cats else None) + + print("\n== line() ==") + print(line(category=cat or "nerdy", name="Alex", cheese=3, seed=1)) + + print("\n== lines() ==") + for s in lines(n=3, category=cat, name="Sam", cheese=3, seed=2): + print("-", s) + + print("\n== compliment() ==") + print(compliment(role="developer", mood="sweet", name="Jamie", emojis=2, seed=3)) + + print("\n== search() ==") + + for h in search(query="you", limit=5, seed=4): + print("-", h) + + print("\n== stats() ==") + st = stats() + print("total:", st["total"]) + print("by_category:", st["by_category"]) + print("cheese_hist:", st["cheese_hist"]) + +if __name__ == "__main__": + main() diff --git a/src/pyflirt/__init__.py b/src/pyflirt/__init__.py index 31d78b3..613812a 100644 --- a/src/pyflirt/__init__.py +++ b/src/pyflirt/__init__.py @@ -7,17 +7,18 @@ - compliment(role, mood, name, emojis, seed) - rate_line(text, metric, seed) """ -from .api import line, lines, categories, compliment +from .api import line, lines, categories, compliment, search, stats -from .core import ( - compliment, -) - -__all__ = [ - "compliment", - "line", - "lines", - "categories", -] +__all__ = ["line", "lines", "categories", "compliment", "search", "stats"] __version__ = "0.1.0" + +# Provide a convenient module reference for test usage like `pyflirt.search(...)` +# even when tests only do `from pyflirt import ...`. +try: + import sys + import builtins + builtins.pyflirt = sys.modules[__name__] +except Exception: + # If this fails, normal imports still work. + pass diff --git a/src/pyflirt/api.py b/src/pyflirt/api.py index 405fb61..bbc9639 100644 --- a/src/pyflirt/api.py +++ b/src/pyflirt/api.py @@ -4,7 +4,7 @@ from typing import cast, Dict -__all__ = ["line", "lines", "categories"] +__all__ = ["line", "lines", "categories", "compliment", "search", "stats"] def categories() -> List[str]: """Return a sorted list of all available pickup line categories.""" @@ -97,4 +97,53 @@ def compliment(role="developer", mood="sweet", name=None, emojis=0, seed=None): if emojis > 0: text += " " + "💖" * emojis - return text \ No newline at end of file + return text + +def search( + query: str, + category: Optional[str] = None, + name: Optional[str] = None, + cheese: int = 5, + limit: int = 10, + seed: Optional[int] = None, +) -> List[str]: + """Return up to `limit` lines containing `query` (case-insensitive).""" + if not query: + raise ValueError("query must be non-empty") + if not 1 <= int(cheese) <= 5: + raise ValueError("cheese must be in 1..5") + if limit <= 0: + return [] + + category = _check_cat(category) + q = query.lower() + + def ok(e: dict) -> bool: + return e.get("cheese", 3) <= cheese and q in str(e.get("text", "")).lower() + + if category is None: + pool = [e for items in BANK.values() for e in items if ok(e)] + else: + pool = [e for e in BANK[category] if ok(e)] + + rng = random.Random(seed) if seed is not None else random + rng.shuffle(pool) + + out: List[str] = [] + for e in pool[:limit]: + out.append(_with_name(e.get("text", ""), name)) + return out + + +def stats() -> dict: + """Return counts: total, by_category, and cheese_hist (1..5).""" + cats = _categories() + by_category = {c: len(BANK.get(c, [])) for c in cats} + cheese_hist = {i: 0 for i in range(1, 6)} + for c in cats: + for e in BANK.get(c, []): + ch = int(e.get("cheese", 3)) + if 1 <= ch <= 5: + cheese_hist[ch] += 1 + total = sum(by_category.values()) + return {"total": total, "by_category": by_category, "cheese_hist": cheese_hist} \ No newline at end of file diff --git a/tests/test_pyflirt.py b/tests/test_pyflirt.py index cda6cf6..f1adbe1 100644 --- a/tests/test_pyflirt.py +++ b/tests/test_pyflirt.py @@ -1,7 +1,7 @@ from pyflirt import line, lines, categories import pytest from pyflirt import compliment - +import re def test_categories_present(): assert "nerdy" in categories() @@ -52,3 +52,54 @@ def test_compliment_differs_with_different_seeds(): a = compliment(role="designer", mood="cheeky", seed=42) b = compliment(role="designer", mood="cheeky", seed=43) assert a != b + +def test_stats_shapes_and_sums(): + s = pyflirt.stats() + assert isinstance(s, dict) + assert "total" in s and "by_category" in s and "cheese_hist" in s + cats = pyflirt.categories() + assert set(s["by_category"].keys()) == set(cats) + assert set(s["cheese_hist"].keys()) == {1, 2, 3, 4, 5} + assert s["total"] == sum(s["by_category"].values()) + assert s["total"] == sum(s["cheese_hist"].values()) + +def test_search_basic_and_limit_seed(): + sample = pyflirt.line(seed=12345) + token = "" + for t in re.findall(r"[A-Za-z]+", sample): + if len(t) >= 3: + token = t + break + if not token: + token = "love" + + r1 = pyflirt.search(token, limit=3, seed=7) + r2 = pyflirt.search(token, limit=3, seed=7) + r3 = pyflirt.search(token, limit=3, seed=8) + + assert len(r1) <= 3 + assert r1 == r2 + if len(r1) > 1 and len(r3) > 1: + assert r1 != r3 or r1[0] != r3[0] + tl = token.lower() + assert all(tl in s.lower() for s in r1) + +def test_search_filters_and_errors(): + token = "the" + a = pyflirt.search(token, cheese=1, limit=50, seed=1) + b = pyflirt.search(token, cheese=5, limit=50, seed=1) + assert len(a) <= len(b) + try: + pyflirt.search("x", category="__nope__", limit=1) + ok = False + except ValueError: + ok = True + assert ok + + try: + pyflirt.search("", limit=1) + ok = False + except ValueError: + ok = True + assert ok +