From c235b93ab5d46e3767dc674cd821c60949704f66 Mon Sep 17 00:00:00 2001 From: SinaL0123 Date: Thu, 30 Oct 2025 19:34:18 -0400 Subject: [PATCH 01/19] Port files from origin/SinaL0123 onto pipfile-experiment base --- .github/note.txt | 2 - .github/workflows/Pipfile | 15 - .github/workflows/Pipfile.lock | 184 ------------ .github/workflows/build.yaml | 40 +++ .github/workflows/event-logger.yml | 54 ---- .github/workflows/requirements.txt | 10 - .gitignore | 12 +- Pipfile | 18 ++ Pipfile.lock | 467 +++++++++++++++++++++++++++++ README.md | 144 ++++++++- instructions.md | 62 ---- pyproject.toml | 36 +++ src/pyfortunecookie/__init__.py | 13 + src/pyfortunecookie/__main__.py | 15 + src/pyfortunecookie/core.py | 39 +++ tests/__init__.py | 0 tests/test_core.py | 17 ++ 17 files changed, 797 insertions(+), 331 deletions(-) delete mode 100644 .github/note.txt delete mode 100644 .github/workflows/Pipfile delete mode 100644 .github/workflows/Pipfile.lock create mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/event-logger.yml delete mode 100644 .github/workflows/requirements.txt create mode 100644 Pipfile create mode 100644 Pipfile.lock delete mode 100644 instructions.md create mode 100644 pyproject.toml create mode 100644 src/pyfortunecookie/__init__.py create mode 100644 src/pyfortunecookie/__main__.py create mode 100644 src/pyfortunecookie/core.py create mode 100644 tests/__init__.py create mode 100644 tests/test_core.py diff --git a/.github/note.txt b/.github/note.txt deleted file mode 100644 index 0dde06d..0000000 --- a/.github/note.txt +++ /dev/null @@ -1,2 +0,0 @@ -The files in this directory contain configuration settings for GitHub Actions (https://docs.github.com/en/actions). -Do not modify the given files, although you are welcome to add additional files as needed. diff --git a/.github/workflows/Pipfile b/.github/workflows/Pipfile deleted file mode 100644 index f5d05e5..0000000 --- a/.github/workflows/Pipfile +++ /dev/null @@ -1,15 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -gitcommitlogger = "*" -requests = "*" -pytz = "*" -python-dateutil = "*" - -[dev-packages] - -[requires] -python_version = "3" diff --git a/.github/workflows/Pipfile.lock b/.github/workflows/Pipfile.lock deleted file mode 100644 index d1e37b9..0000000 --- a/.github/workflows/Pipfile.lock +++ /dev/null @@ -1,184 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "688050f13174cb5c5295b49799f3603a60d03b27fadcc1c63d8ce8b0ae632293" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "certifi": { - "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" - ], - "markers": "python_version >= '3.6'", - "version": "==2024.2.2" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "gitcommitlogger": { - "hashes": [ - "sha256:3badf5db541d81cc3473882d9d255dabc66cd413e780ccb4fe6b320afddedcd0", - "sha256:7bafd58d46441b8c01b0f5706e7242df0ff841b3f87a7f621979577758546869" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==1.2.5" - }, - "idna": { - "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" - ], - "markers": "python_version >= '3.5'", - "version": "==3.6" - }, - "python-dateutil": { - "hashes": [ - "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", - "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" - ], - "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.9.0.post0" - }, - "pytz": { - "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" - ], - "index": "pypi", - "version": "==2024.1" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.16.0" - }, - "urllib3": { - "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" - ], - "markers": "python_version >= '3.8'", - "version": "==2.2.1" - } - }, - "develop": {} -} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..5c33473 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,40 @@ +name: CI / CD +on: [push] +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + matrix: + python-version: ["3.9","3.11"] + steps: + - uses: actions/checkout@v4 + - name: Install Python, pipenv and Pipfile packages + uses: kojoru/prepare-pipenv@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Turn on 'editable' mode + run: | + pipenv install -e . + - name: Test with pytest + run: | + pipenv install pytest + pipenv --venv + pipenv run python -m pytest + deliver: + needs: [build] + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - name: Install Python, pipenv and Pipfile packages + uses: kojoru/prepare-pipenv@v1 + - name: Build package + run: | + pipenv install build + pipenv run python -m build . + - name: Publish to PyPI test server + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/event-logger.yml b/.github/workflows/event-logger.yml deleted file mode 100644 index 31f231e..0000000 --- a/.github/workflows/event-logger.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: log github events -on: - push: - branches: [main, master, pipfile-experiment] - pull_request: - types: [opened, closed] - branches: [main, master, pipfile-experiment] -jobs: - log: - runs-on: ubuntu-latest - env: - PIPENV_PIPFILE: .github/workflows/Pipfile # so this script doesn't use the Pipfile in the root directory - COMMIT_LOG_API: ${{ secrets.COMMIT_LOG_API }} - GITHUB_LOGIN: ${{ github.actor }} # github login also available in github.triggering_actor, github.event.sender.login - COMMITS: ${{ toJSON(github.event.commits) }} - REPOSITORY_URL: ${{ github.repositoryUrl }} - EVENT_TYPE: ${{ github.event_name }} - EVENT_ACTION: ${{ github.event.action }} - PR_MERGED: ${{ github.event.pull_request.merged }} - PR_CREATED_AT: ${{ github.event.pull_request.created_at}} - PR_CLOSED_AT: ${{ github.event.pull_request.closed_at}} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # this is important so git fetches all history.. the actions/checkout by default fetches all history as one commit which throws off stats - - uses: actions/setup-python@v3 - with: - python-version: "^3.9" - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --user pipenv - pipenv --python $(which python) - pipenv install - - name: Log pull request opened - if: github.event_name == 'pull_request' && github.event.action == 'opened' - run: | - pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t pull_request_opened -d $(echo $PR_CREATED_AT) -un $(echo $GITHUB_LOGIN) -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v - - name: Log pull request closed and merged - if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == true - run: | - echo $COMMITS > commits.json - cat commits.json # debugging - pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t pull_request_merged -d $(echo $PR_CLOSED_AT) -un $(echo $GITHUB_LOGIN) -i commits.json -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v - - name: Log pull request closed without merge - if: github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == false - run: | - pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t pull_request_closed -d $(echo $PR_CLOSED_AT) -un $(echo $GITHUB_LOGIN) -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v - - name: Log push - if: github.event_name == 'push' - run: | - echo $COMMITS > commits.json - cat commits.json # debugging - pipenv run gitcommitlogger -r $(echo $REPOSITORY_URL) -t $(echo $EVENT_TYPE) -i commits.json -o commit_stats.csv -u $(echo $COMMIT_LOG_API) -v diff --git a/.github/workflows/requirements.txt b/.github/workflows/requirements.txt deleted file mode 100644 index d055863..0000000 --- a/.github/workflows/requirements.txt +++ /dev/null @@ -1,10 +0,0 @@ --i https://pypi.org/simple -certifi==2024.2.2; python_version >= '3.6' -charset-normalizer==3.3.2; python_full_version >= '3.7.0' -gitcommitlogger==1.2.5; python_version >= '3.7' -idna==3.6; python_version >= '3.5' -python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -pytz==2024.1 -requests==2.31.0; python_version >= '3.7' -six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' -urllib3==2.2.1; python_version >= '3.8' diff --git a/.gitignore b/.gitignore index 2f24a10..8274335 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ +# ignore any cloned repos +repos/ + +# ignore generated reports +reports/ + # mac junk .DS_Store @@ -16,6 +22,9 @@ __pycache__/ # C extensions *.so +# PyPI authentication +*pypirc + # Distribution / packaging .Python build/ @@ -30,7 +39,6 @@ parts/ sdist/ var/ wheels/ -pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg @@ -96,7 +104,7 @@ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -.python-version +# .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..431a7ef --- /dev/null +++ b/Pipfile @@ -0,0 +1,18 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +examplepackagefb1258 = {file = ".", editable = true} +coverage = "*" +pytest-cov = "*" +twine = "*" +pyfortunecookie = {file = ".", editable = true} +pytest = "*" +build = "*" + +[dev-packages] + +[requires] +python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..4c8690f --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,467 @@ +{ + "_meta": { + "hash": { + "sha256": "1dc77301e5beac6f24141e19814fde23115cd9885d34a70d60ad0dcba89f458c" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "build": { + "hashes": [ + "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397", + "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.3.0" + }, + "certifi": { + "hashes": [ + "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", + "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" + ], + "markers": "python_version >= '3.6'", + "version": "==2025.1.31" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", + "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", + "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", + "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", + "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", + "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", + "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", + "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", + "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", + "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", + "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", + "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", + "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", + "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", + "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", + "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", + "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", + "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", + "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", + "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", + "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", + "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", + "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", + "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", + "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", + "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", + "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", + "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", + "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", + "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", + "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", + "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", + "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", + "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", + "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", + "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", + "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", + "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", + "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", + "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", + "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", + "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", + "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", + "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", + "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", + "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", + "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", + "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", + "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", + "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", + "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", + "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", + "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", + "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", + "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", + "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", + "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", + "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", + "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", + "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", + "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", + "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", + "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", + "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", + "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", + "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", + "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", + "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", + "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", + "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", + "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", + "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", + "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", + "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", + "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", + "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", + "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", + "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", + "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", + "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", + "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", + "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", + "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", + "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", + "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", + "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", + "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", + "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", + "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", + "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", + "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", + "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.1" + }, + "coverage": { + "extras": [ + "toml" + ], + "hashes": [ + "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5", + "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e", + "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab", + "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a", + "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82", + "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050", + "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893", + "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de", + "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969", + "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601", + "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de", + "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05", + "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3", + "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad", + "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841", + "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847", + "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52", + "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11", + "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68", + "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73", + "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415", + "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745", + "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b", + "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785", + "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4", + "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48", + "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2", + "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b", + "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040", + "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0", + "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866", + "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1", + "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf", + "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b", + "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d", + "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115", + "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2", + "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9", + "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43", + "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc", + "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa", + "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1", + "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088", + "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c", + "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777", + "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623", + "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268", + "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186", + "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e", + "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007", + "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7", + "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547", + "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3", + "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc", + "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37", + "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721", + "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552", + "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca", + "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497", + "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46", + "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2", + "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0", + "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44", + "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1", + "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4", + "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479", + "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e", + "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2", + "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e", + "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef", + "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0", + "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad", + "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075", + "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f", + "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d", + "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca", + "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd", + "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287", + "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f", + "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0", + "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996", + "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d", + "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd", + "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31", + "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740", + "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591", + "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be", + "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f", + "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d", + "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836", + "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d", + "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c" + ], + "index": "pypi", + "markers": "python_version >= '3.10'", + "version": "==7.11.0" + }, + "docutils": { + "hashes": [ + "sha256:3a6b18732edf182daa3cd12775bbb338cf5691468f91eeeb109deff6ebfa986f", + "sha256:dafca5b9e384f0e419294eb4d2ff9fa826435bf15f15b7bd45723e8ad76811b2" + ], + "markers": "python_version >= '3.9'", + "version": "==0.21.2" + }, + "id": { + "hashes": [ + "sha256:292cb8a49eacbbdbce97244f47a97b4c62540169c976552e497fd57df0734c1d", + "sha256:f1434e1cef91f2cbb8a4ec64663d5a23b9ed43ef44c4c957d02583d61714c658" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" + }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, + "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:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", + "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649" + ], + "markers": "python_version >= '3.8'", + "version": "==4.1.0" + }, + "keyring": { + "hashes": [ + "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", + "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd" + ], + "markers": "platform_machine != 'ppc64le' and platform_machine != 's390x'", + "version": "==25.6.0" + }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, + "more-itertools": { + "hashes": [ + "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b", + "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89" + ], + "markers": "python_version >= '3.9'", + "version": "==10.6.0" + }, + "nh3": { + "hashes": [ + "sha256:087ffadfdcd497658c3adc797258ce0f06be8a537786a7217649fc1c0c60c293", + "sha256:20979783526641c81d2f5bfa6ca5ccca3d1e4472474b162c6256745fbfe31cd1", + "sha256:2a5174551f95f2836f2ad6a8074560f261cf9740a48437d6151fd2d4d7d617ab", + "sha256:31eedcd7d08b0eae28ba47f43fd33a653b4cdb271d64f1aeda47001618348fde", + "sha256:4990e7ee6a55490dbf00d61a6f476c9a3258e31e711e13713b2ea7d6616f670e", + "sha256:55823c5ea1f6b267a4fad5de39bc0524d49a47783e1fe094bcf9c537a37df251", + "sha256:6141caabe00bbddc869665b35fc56a478eb774a8c1dfd6fba9fe1dfdf29e6efa", + "sha256:637d4a10c834e1b7d9548592c7aad760611415fcd5bd346f77fd8a064309ae6d", + "sha256:63ca02ac6f27fc80f9894409eb61de2cb20ef0a23740c7e29f9ec827139fa578", + "sha256:6ae319f17cd8960d0612f0f0ddff5a90700fa71926ca800e9028e7851ce44a6f", + "sha256:6c9c30b8b0d291a7c5ab0967ab200598ba33208f754f2f4920e9343bdd88f79a", + "sha256:713d16686596e556b65e7f8c58328c2df63f1a7abe1277d87625dcbbc012ef82", + "sha256:818f2b6df3763e058efa9e69677b5a92f9bc0acff3295af5ed013da544250d5b", + "sha256:9d67709bc0d7d1f5797b21db26e7a8b3d15d21c9c5f58ccfe48b5328483b685b", + "sha256:a5f77e62aed5c4acad635239ac1290404c7e940c81abe561fd2af011ff59f585", + "sha256:a772dec5b7b7325780922dd904709f0f5f3a79fbf756de5291c01370f6df0967", + "sha256:a7ea28cd49293749d67e4fcf326c554c83ec912cd09cd94aa7ec3ab1921c8283", + "sha256:ac7006c3abd097790e611fe4646ecb19a8d7f2184b882f6093293b8d9b887431", + "sha256:b3b5c58161e08549904ac4abd450dacd94ff648916f7c376ae4b2c0652b98ff9", + "sha256:b8d55ea1fc7ae3633d758a92aafa3505cd3cc5a6e40470c9164d54dff6f96d42", + "sha256:bb0014948f04d7976aabae43fcd4cb7f551f9f8ce785a4c9ef66e6c2590f8629", + "sha256:d002b648592bf3033adfd875a48f09b8ecc000abd7f6a8769ed86b6ccc70c759", + "sha256:d426d7be1a2f3d896950fe263332ed1662f6c78525b4520c8e9861f8d7f0d243", + "sha256:fcff321bd60c6c5c9cb4ddf2554e22772bb41ebd93ad88171bbbb6f271255286" + ], + "markers": "python_version >= '3.8'", + "version": "==0.2.21" + }, + "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" + }, + "pyfortunecookie": { + "editable": true, + "file": "." + }, + "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" + }, + "pytest-cov": { + "hashes": [ + "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", + "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==7.0.0" + }, + "readme-renderer": { + "hashes": [ + "sha256:2fbca89b81a08526aadf1357a8c2ae889ec05fb03f5da67f9769c9a592166151", + "sha256:8712034eabbfa6805cacf1402b4eeb2a73028f72d1166d6f5cb7f9c047c5d1e1" + ], + "markers": "python_version >= '3.9'", + "version": "==44.0" + }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, + "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:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", + "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==13.9.4" + }, + "twine": { + "hashes": [ + "sha256:a47f973caf122930bf0fbbf17f80b83bc1602c9ce393c7845f289a3001dc5384", + "sha256:be324f6272eff91d07ee93f251edf232fc647935dd585ac003539b42404a8dbd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.1.0" + }, + "urllib3": { + "hashes": [ + "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", + "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + ], + "markers": "python_version >= '3.9'", + "version": "==2.3.0" + } + }, + "develop": {} +} diff --git a/README.md b/README.md index 6022e0e..f9ed7bd 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,143 @@ -# Python Package Exercise +# PyFortuneCookie -An exercise to create a Python package, build it, test it, distribute it, and use it. See [instructions](./instructions.md) for details. +A fun Python package that generates your **daily fortune cookie** — complete with a lucky number and color! +Perfect for learning how to build and run Python packages. + +--- + +## Installation + +Clone or download this repository, then install it locally (in editable mode): + +```bash +pipenv install +pipenv run pip install -e . +```` + +If you don’t have **pipenv**, install it first: + +```bash +pip install pipenv +``` + +--- + +## Run Locally (for teammates) + +If you want to run this project on your own machine (e.g., to test or modify it): + +```bash +# 1️⃣ Clone the repository +git clone https://github.com/swe-students-fall2025/3-python-package-team_meridian.git + +cd pyfortunecookie + +# 2️⃣ Install pipenv and dependencies +pip install pipenv +pipenv install + +# 3️⃣ Enter the virtual environment +pipenv shell + +# 4️⃣ Run the tests (optional, to verify everything works) +pipenv run pytest + +# 5️⃣ Run the package +python3 -m pyfortunecookie +``` + +💡 You can exit the environment anytime with: + +```bash +exit +``` + +--- + +## Usage + +You can run PyFortuneCookie either from the **command line** or directly as a **Python module**. + +### ▶️ Option 1: Run as command-line tool + +```bash +pipenv run pyfortunecookie +``` + +### ▶️ Option 2: Run as a Python module + +```bash +pipenv run python -m pyfortunecookie +``` + +### ▶️ Option 3: Import and use functions in your code + +```python +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color + +print(get_fortune()) +print(get_lucky_number()) +print(get_color()) +``` + +--- + +## Example Output + +When you run the command: + +```bash +pipenv run pyfortunecookie +``` + +You might see something like this: + +``` +🥠 Welcome to PyFortune Cookie! + +Today's fortune: Your curiosity will lead to something amazing today ✨ +Your lucky number: 37 +Your lucky color: Lavender +``` + +Each time you run it, you’ll get a new random fortune, number, and color! + +--- + +## Features + +| Function | Description | +| -------------------- | -------------------------------- | +| `get_fortune()` | Returns a random fortune message | +| `get_lucky_number()` | Generates a random lucky number | +| `get_color()` | Returns a random lucky color | + +--- + +## Run Tests + +Make sure everything works properly with: + +```bash +pipenv run pytest +``` + +All tests are located inside the `tests/` directory. + +--- + +## Project Structure + +``` +pyfortunecookie/ +├── src/ +│ └── pyfortunecookie/ +│ ├── __init__.py +│ ├── __main__.py +│ └── core.py +├── tests/ +│ └── test_core.py +├── Pipfile +├── pyproject.toml +└── README.md +``` \ No newline at end of file diff --git a/instructions.md b/instructions.md deleted file mode 100644 index dc03e03..0000000 --- a/instructions.md +++ /dev/null @@ -1,62 +0,0 @@ -# Python Package Exercise - -A little exercise to create a Python package, build it, test it, distribute it, and use it. - -## Concept - -A _package_ is software that has been built, tested, and distributed in a way that makes it easy for other developers to download, install, and configure to add extra functionality to their own programs. Your job is to create a Python package that brings a little bit of joy and levity to the lives of other developers. The package you create should be lighthearted and not "serious" software. However, you will create it following _rigorous_ software engineering practices. - -## Inspiration - -Some inspirational Python packages, for example: - -- [pyjokes](https://pypi.org/project/pyjokes/) -- [pyfiglet](https://github.com/pwaller/pyfiglet) -- [pycowsay](https://pypi.org/project/pycowsay/) -- [emoji](https://pypi.org/project/emoji/) -- [python-lorem](https://pypi.org/project/python-lorem/) -- [horoscope](https://pypi.org/project/horoscope/) -- [freegames](https://pypi.org/project/freegames/) - -## Requirements - -Create a Python package with at least **four functions that accept arguments** which influence their behavior. The package must be distributed in the [PyPI](https://pypi.org/) repository and installable via [pip](https://pypi.org/project/pip/). - -- Use [pipenv](https://packaging.python.org/en/latest/tutorials/managing-dependencies/) to manage the package dependencies and virtual environments with a `Pipfile`. -- Use [pytest](https://docs.pytest.org/en/latest/) to write and run tests to validate that your package code behaves as expected. Create as many tests as necessary to thorooughly verify each function's expected behavior - this should be no fewer than three tests per package function. -- Use [build](https://pypa-build.readthedocs.io/en/stable/index.html) to create the package artifacts. -- Use [twine](https://pypi.org/project/twine/) to upload the package to PyPI. -- Use [GitHub Actions](https://github.com/actions) to build your package and run your tests on two different recent versions of Python with every pull request to the `main` branch of your GitHub repository. - -Create an example program that uses all functions of your package and demonstrates its complete functionality. - -## Developer workflow - -All team members must have visibly contributed to the code using their own git & GitHub accounts in order to claim that they contributed to the project. - -All code changes must be done in feature branches and not directly in the `main` branch. - -To merge code from a feature branch into the `main` branch, do the following: - -1. Create a pull request from the feature branch to the `main` branch. -1. Ask a fellow developer to review your code. -1. The reviewer must review the code and run unit tests to verify that the functions behave as expepcted. -1. If the reviewer has any concerns, discuss then and make any changes agreed upon. -1. Merge the pull request into the `main` branch. -1. Delete the feature branch. -1. Pull the latest changes from the remote `main` branch to your local `main` branch. - -**Warning**: the longer you let code sit in a feature branch, the more likely your team is to end up in [merge hell](https://en.wikipedia.org/wiki/Merge_hell). . Merge feature branches into `main` often to avoid this fate. - -## Documentation - -Replace the contents of the [README.md](./README.md) file with a beautifully-formatted Markdown file including a plain-language **description** of your project and **clear instructions**, including exact **code examples**, for: - -- how a developer who wants to import your project into their own code can do so - include documentation for all functions in your package and a link to an example Python program that uses each of them. -- how a developer who wants to contribute to your project can set up the virtual environment, install dependencies, and build and test your package for themselves. - -Include a [badge](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge) at the top of the `README.md` file showing the result of the latest build/test workflow run. - -Include the names of all teammates as links to their GitHub profiles in the README.md file. - -Include a link to your package's page on the PyPI website. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..219dd93 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,36 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "pyfortunecookie" +description = "A fun fortune cookie generator with lucky number and color" +version = "0.1.1" +authors = [ + { name="SinaL0123", email="sl9608@nyu.edu" }, + { name = "DplayerXAX" }, + { name = "aayanmathur" }, + { name = "TogawaSaki0214" }, + { name = "TickyTacky" } +] +license = { file = "LICENSE" } +readme = "README.md" +keywords = ["fortune", "fun", "demo"] +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Python :: 3", + "Intended Audience :: Education", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Operating System :: OS Independent", +] + +[project.optional-dependencies] +dev = ["pytest"] + +[project.urls] +Homepage = "https://github.com/swe-students-fall2025/3-python-package-team_meridian" +Repository = "https://github.com/swe-students-fall2025/3-python-package-team_meridian.git" +"Bug Tracker" = "https://github.com/swe-students-fall2025/3-python-package-team_meridian/issues" + +[project.scripts] +pyfortunecookie = "pyfortunecookie.__main__:main" diff --git a/src/pyfortunecookie/__init__.py b/src/pyfortunecookie/__init__.py new file mode 100644 index 0000000..73417de --- /dev/null +++ b/src/pyfortunecookie/__init__.py @@ -0,0 +1,13 @@ +""" +This file is required for Python to recognize the directory as a "regular" package. + +Python "regular" packages use the __init__.py file to initialize the package and +can include package-level variables, import statements, and initialization code +you wish to run when the package is first imported into a program. + +For more information, see the official documentation: +https://docs.python.org/3/reference/import.html#regular-packages +""" +from .core import get_fortune, get_lucky_number, get_color + +__all__ = ["get_fortune", "get_lucky_number", "get_color"] diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py new file mode 100644 index 0000000..466d7e0 --- /dev/null +++ b/src/pyfortunecookie/__main__.py @@ -0,0 +1,15 @@ +""" +In Python packages, this file called __main__.py is run when the package is run +directly from command line, as opposed to importing it into another program. +""" + +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color + +def main(): + print("🥠 Welcome to PyFortune Cookie!\n") + print(f"Today's fortune: {get_fortune()}") + print(f"Your lucky number: {get_lucky_number()}") + print(f"Your lucky color: {get_color()}") + +if __name__ == "__main__": + main() diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py new file mode 100644 index 0000000..d17f51c --- /dev/null +++ b/src/pyfortunecookie/core.py @@ -0,0 +1,39 @@ +import random +from typing import Optional + + +_FORTUNES = [ + "Today is a good day to start small.", + "A pleasant surprise is waiting for you.", + "Your code will compile on the first try.", + "Help others and luck will help you.", + "Take a short walk; ideas will follow.", + "A cup of coffee will solve half your problems. ☕", + "You will soon discover a hidden strength. 💪", + "Your curiosity is your superpower. 🔍" +] + +_PALETTES = { + "soft": ["peach", "mint", "lavender", "sky", "lemon"], + "bold": ["crimson", "indigo", "emerald", "amber", "teal"], + "mono": ["black", "white", "gray"] +} + +def get_fortune(rng: Optional[random.Random] = None) -> str: + """Return a random fortune sentence.""" + rng = rng or random + return rng.choice(_FORTUNES) + +def get_lucky_number(seed: Optional[int] = None, min_value: int = 1, max_value: int = 99) -> int: + """Return a lucky number (optionally deterministic if seed provided).""" + if min_value > max_value: + raise ValueError("min_value must be <= max_value") + rng = random.Random(seed) if seed is not None else random + return rng.randint(min_value, max_value) + +def get_color(palette: str = "soft", rng: Optional[random.Random] = None) -> str: + """Return a lucky color from the selected palette.""" + if palette not in _PALETTES: + raise ValueError(f"Unknown palette '{palette}'. Valid: {', '.join(_PALETTES)}") + rng = rng or random + return rng.choice(_PALETTES[palette]) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..7e07146 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,17 @@ +import pytest +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color + +def test_get_fortune(): + result = get_fortune() + assert isinstance(result, str) + assert len(result) > 0 + +def test_get_lucky_number(): + num = get_lucky_number() + assert isinstance(num, int) + assert 1 <= num <= 99 + +def test_get_color(): + color = get_color() + assert isinstance(color, str) + assert len(color) > 0 From 4da5f208255b01e0aa9527e89253a691e71e3a9b Mon Sep 17 00:00:00 2001 From: Dplayerx_X Date: Sat, 1 Nov 2025 15:22:10 -0400 Subject: [PATCH 02/19] add tarot function --- src/pyfortunecookie/core.py | 56 +++++++++++++++++++++++++++++++++++++ tests/test_core.py | 4 ++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index d17f51c..019b96c 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -1,7 +1,43 @@ +from os import nice import random from typing import Optional +_TAROT = { + "The Fool": "New beginnings, take a leap of faith.", + "The Magician": "Your skills will shape reality.", + "The High Priestess": "Trust your intuition.", + "The Empress": "Abundance grows where you nurture.", + "The Emperor": "Structure brings stability.", + "The Hierophant": "Tradition and guidance shape your path.", + "The Lovers": "Meaningful connections influence your decisions.", + "The Chariot": "Focus and willpower drive victory.", + "Strength": "Quiet courage overcomes fear.", + "The Hermit": "Solitude reveals deeper answers.", + "Wheel of Fortune": "Things will shift—be adaptable.", + "Justice": "Balance and fairness will restore order.", + "The Hanged Man": "Change perspective to see clearly.", + "Death": "A chapter ends so something new can begin.", + "Temperance": "Moderation creates harmony.", + "The Devil": "Beware of illusions and temptation.", + "The Tower": "Old structures must fall before renewal.", + "The Star": "Hope quietly returns.", + "The Moon": "The truth is hidden beneath uncertainty.", + "The Sun": "Joy, clarity, and success await.", + "Judgement": "Your past transforms into resolution.", + "The World": "Completion brings fulfillment." +} +_TAROT_GROUP = { + "seeking_change": [ + "The Fool", "The Tower", "Death", "The Chariot", "Judgement", "Wheel of Fortune" + ], + "needing_clarity": [ + "The High Priestess", "The Moon", "The Hermit", "The Hanged Man", "Justice" + ], + "needing_support": [ + "Strength", "Temperance", "The Star", "The Sun", "The Empress", "The World" + ] +} _FORTUNES = [ "Today is a good day to start small.", "A pleasant surprise is waiting for you.", @@ -37,3 +73,23 @@ def get_color(palette: str = "soft", rng: Optional[random.Random] = None) -> str raise ValueError(f"Unknown palette '{palette}'. Valid: {', '.join(_PALETTES)}") rng = rng or random return rng.choice(_PALETTES[palette]) + +def get_tarot_reading(intent: Optional[str] = None, rng: Optional[random.Random] = None) -> str: + """ + Return a tarot reading with slight bias based on intent. + Valid intents: seeking_change, needing_clarity, needing_support + """ + rng = rng or random + + if intent in _TAROT_GROUP: + if rng.random() < 0.7: + pool = _TAROT_GROUP[intent] + else: + pool = [c for c in _TAROT.keys() if c not in _TAROT_GROUP[intent]] + else: + pool = list(_TAROT.keys()) + + card = rng.choice(pool) + meaning = _TAROT[card] + return f"{card}: {meaning}" + diff --git a/tests/test_core.py b/tests/test_core.py index 7e07146..7042d25 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ import pytest -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading def test_get_fortune(): result = get_fortune() @@ -15,3 +15,5 @@ def test_get_color(): color = get_color() assert isinstance(color, str) assert len(color) > 0 + + From f16ef1b96d125ab3a6986e21f22c4cc18821e83e Mon Sep 17 00:00:00 2001 From: DplayerXAX Date: Sat, 1 Nov 2025 15:22:10 -0400 Subject: [PATCH 03/19] add tarot function --- src/pyfortunecookie/core.py | 56 +++++++++++++++++++++++++++++++++++++ tests/test_core.py | 4 ++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index d17f51c..019b96c 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -1,7 +1,43 @@ +from os import nice import random from typing import Optional +_TAROT = { + "The Fool": "New beginnings, take a leap of faith.", + "The Magician": "Your skills will shape reality.", + "The High Priestess": "Trust your intuition.", + "The Empress": "Abundance grows where you nurture.", + "The Emperor": "Structure brings stability.", + "The Hierophant": "Tradition and guidance shape your path.", + "The Lovers": "Meaningful connections influence your decisions.", + "The Chariot": "Focus and willpower drive victory.", + "Strength": "Quiet courage overcomes fear.", + "The Hermit": "Solitude reveals deeper answers.", + "Wheel of Fortune": "Things will shift—be adaptable.", + "Justice": "Balance and fairness will restore order.", + "The Hanged Man": "Change perspective to see clearly.", + "Death": "A chapter ends so something new can begin.", + "Temperance": "Moderation creates harmony.", + "The Devil": "Beware of illusions and temptation.", + "The Tower": "Old structures must fall before renewal.", + "The Star": "Hope quietly returns.", + "The Moon": "The truth is hidden beneath uncertainty.", + "The Sun": "Joy, clarity, and success await.", + "Judgement": "Your past transforms into resolution.", + "The World": "Completion brings fulfillment." +} +_TAROT_GROUP = { + "seeking_change": [ + "The Fool", "The Tower", "Death", "The Chariot", "Judgement", "Wheel of Fortune" + ], + "needing_clarity": [ + "The High Priestess", "The Moon", "The Hermit", "The Hanged Man", "Justice" + ], + "needing_support": [ + "Strength", "Temperance", "The Star", "The Sun", "The Empress", "The World" + ] +} _FORTUNES = [ "Today is a good day to start small.", "A pleasant surprise is waiting for you.", @@ -37,3 +73,23 @@ def get_color(palette: str = "soft", rng: Optional[random.Random] = None) -> str raise ValueError(f"Unknown palette '{palette}'. Valid: {', '.join(_PALETTES)}") rng = rng or random return rng.choice(_PALETTES[palette]) + +def get_tarot_reading(intent: Optional[str] = None, rng: Optional[random.Random] = None) -> str: + """ + Return a tarot reading with slight bias based on intent. + Valid intents: seeking_change, needing_clarity, needing_support + """ + rng = rng or random + + if intent in _TAROT_GROUP: + if rng.random() < 0.7: + pool = _TAROT_GROUP[intent] + else: + pool = [c for c in _TAROT.keys() if c not in _TAROT_GROUP[intent]] + else: + pool = list(_TAROT.keys()) + + card = rng.choice(pool) + meaning = _TAROT[card] + return f"{card}: {meaning}" + diff --git a/tests/test_core.py b/tests/test_core.py index 7e07146..7042d25 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ import pytest -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading def test_get_fortune(): result = get_fortune() @@ -15,3 +15,5 @@ def test_get_color(): color = get_color() assert isinstance(color, str) assert len(color) > 0 + + From 4494e4d0002b2adf85a034b3f37297eeea387478 Mon Sep 17 00:00:00 2001 From: DplayerXAX Date: Sat, 1 Nov 2025 15:34:21 -0400 Subject: [PATCH 04/19] add and run test case for tarot --- tests/test_core.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 7042d25..54bc4fb 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -17,3 +17,18 @@ def test_get_color(): assert len(color) > 0 +def test_get_tarot_reading_basic(): + text = get_tarot_reading() + assert isinstance(text, str) + assert ":" in text + assert len(text) > 0 + +def test_get_tarot_reading_with_intent(): + text = get_tarot_reading(intent="needing_clarity") + assert isinstance(text, str) + assert ":" in text + +def test_get_tarot_reading_invalid_intent_fallback(): + text = get_tarot_reading(intent="nonsense_value") + assert isinstance(text, str) + assert ":" in text From 21a84d81859dfebe43c44fa29be08433a0abc826 Mon Sep 17 00:00:00 2001 From: DplayerXAX Date: Sat, 1 Nov 2025 15:37:50 -0400 Subject: [PATCH 05/19] add tarot into main --- src/pyfortunecookie/__main__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py index 466d7e0..2fdd97d 100644 --- a/src/pyfortunecookie/__main__.py +++ b/src/pyfortunecookie/__main__.py @@ -3,13 +3,15 @@ directly from command line, as opposed to importing it into another program. """ -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading def main(): print("🥠 Welcome to PyFortune Cookie!\n") print(f"Today's fortune: {get_fortune()}") print(f"Your lucky number: {get_lucky_number()}") print(f"Your lucky color: {get_color()}") + print("\n🔮 Tarot Reading:") + print(get_tarot_reading()) if __name__ == "__main__": main() From 5465e3bc6382b495dbe94c24839fcca4387f752c Mon Sep 17 00:00:00 2001 From: DplayerXAX Date: Sat, 1 Nov 2025 15:40:36 -0400 Subject: [PATCH 06/19] add permission --- .github/workflows/build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5c33473..aaafe57 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,6 +4,9 @@ jobs: build: runs-on: ubuntu-latest timeout-minutes: 5 + permissions: + id-token: write + contents: read strategy: matrix: python-version: ["3.9","3.11"] From 2b4d7b9a42e7cc3e04063bd8b595d1c695ec7e53 Mon Sep 17 00:00:00 2001 From: aayanmathur Date: Sat, 1 Nov 2025 16:59:45 -0400 Subject: [PATCH 07/19] Add interactive personalized fortune to __main__.py --- src/pyfortunecookie/__init__.py | 2 +- src/pyfortunecookie/__main__.py | 42 ++++++++- src/pyfortunecookie/core.py | 162 ++++++++++++++++++++++++++++++++ tests/test_core.py | 22 ++++- 4 files changed, 225 insertions(+), 3 deletions(-) diff --git a/src/pyfortunecookie/__init__.py b/src/pyfortunecookie/__init__.py index 73417de..e4b7e50 100644 --- a/src/pyfortunecookie/__init__.py +++ b/src/pyfortunecookie/__init__.py @@ -10,4 +10,4 @@ """ from .core import get_fortune, get_lucky_number, get_color -__all__ = ["get_fortune", "get_lucky_number", "get_color"] +__all__ = ["get_fortune", "get_lucky_number", "get_color", "get_tarot_reading", "get_fortune_by_choice"] diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py index 2fdd97d..e57da59 100644 --- a/src/pyfortunecookie/__main__.py +++ b/src/pyfortunecookie/__main__.py @@ -3,7 +3,7 @@ directly from command line, as opposed to importing it into another program. """ -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading, get_fortune_by_choice def main(): print("🥠 Welcome to PyFortune Cookie!\n") @@ -13,5 +13,45 @@ def main(): print("\n🔮 Tarot Reading:") print(get_tarot_reading()) + # Interactive personalized fortune + print("\n🎴 Personalized Fortune (by your choices):") + + # Ask user for their choices + elements = ["fire", "water", "earth", "air"] + times = ["dawn", "noon", "dusk", "midnight"] + symbols = ["star", "moon", "sun", "cloud"] + + while True: + choice1 = input(f"Choose an element {elements}: ").strip().lower() + if choice1 in elements: + break + print(f"Invalid choice. Please choose one of {elements}.") + + while True: + choice2 = input(f"Choose a time {times}: ").strip().lower() + if choice2 in times: + break + print(f"Invalid choice. Please choose one of {times}.") + + while True: + choice3 = input(f"Choose a symbol {symbols}: ").strip().lower() + if choice3 in symbols: + break + print(f"Invalid choice. Please choose one of {symbols}.") + + # Generate personalized fortune + choice_result = get_fortune_by_choice(choice1, choice2, choice3) + + # Print results + print("\n✨ Your Personalized Fortune ✨") + print(f"Element: {choice_result['element']}") + print(f"Time: {choice_result['time']}") + print(f"Symbol: {choice_result['symbol']}") + print(f"Combination: {choice_result['combination']}") + print(f"Fortune: {choice_result['fortune']}") + print(f"Lucky Number: {choice_result['lucky_number']}") + print(f"Lucky Color: {choice_result['lucky_color']}") + + if __name__ == "__main__": main() diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index 019b96c..9b51477 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -93,3 +93,165 @@ def get_tarot_reading(intent: Optional[str] = None, rng: Optional[random.Random] meaning = _TAROT[card] return f"{card}: {meaning}" +def get_fortune_by_choice(choice1: str, choice2: str, choice3: str, rng: Optional[random.Random] = None) -> dict: + """ + Get a personalized fortune based on three interactive choices. + A fun, interactive way to get a fortune that feels tailored to you. + + Args: + choice1: Choose an element - "fire", "water", "earth", or "air" + choice2: Choose a time - "dawn", "noon", "dusk", or "midnight" + choice3: Choose a symbol - "star", "moon", "sun", or "cloud" + rng: Optional random number generator for testing + + Returns: + Dictionary containing: + - fortune: Personalized fortune message + - element: The element chosen + - time: The time chosen + - symbol: The symbol chosen + - combination: A description of what your choices mean + - lucky_number: A number based on your choices + - lucky_color: A color based on your element + + Raises: + ValueError: If any choice is not from the valid options + + Example: + >>> result = get_fortune_by_choice("fire", "dawn", "star") + >>> print(result['fortune']) + "Your fiery spirit at dawn attracts stellar opportunities! ⭐🔥" + """ + # Valid options + valid_elements = ["fire", "water", "earth", "air"] + valid_times = ["dawn", "noon", "dusk", "midnight"] + valid_symbols = ["star", "moon", "sun", "cloud"] + + # Validate inputs + if choice1 not in valid_elements: + raise ValueError(f"Invalid element '{choice1}'. Valid: {', '.join(valid_elements)}") + if choice2 not in valid_times: + raise ValueError(f"Invalid time '{choice2}'. Valid: {', '.join(valid_times)}") + if choice3 not in valid_symbols: + raise ValueError(f"Invalid symbol '{choice3}'. Valid: {', '.join(valid_symbols)}") + + rng = rng or random + + # Element meanings and colors + element_data = { + "fire": { + "trait": "passionate and energetic", + "color": "crimson", + "emoji": "🔥" + }, + "water": { + "trait": "adaptable and intuitive", + "color": "teal", + "emoji": "💧" + }, + "earth": { + "trait": "grounded and stable", + "color": "emerald", + "emoji": "🌍" + }, + "air": { + "trait": "free-spirited and intellectual", + "color": "sky", + "emoji": "💨" + } + } + + # Time meanings + time_data = { + "dawn": { + "meaning": "new beginnings", + "emoji": "🌅" + }, + "noon": { + "meaning": "peak energy and clarity", + "emoji": "☀️" + }, + "dusk": { + "meaning": "reflection and transformation", + "emoji": "🌆" + }, + "midnight": { + "meaning": "mystery and deep insight", + "emoji": "🌙" + } + } + + # Symbol meanings + symbol_data = { + "star": { + "meaning": "guidance and aspiration", + "emoji": "⭐", + "fortunes": [ + "Your path is illuminated by cosmic guidance!", + "Reach for the stars; they're closer than you think!", + "Stellar opportunities align in your favor!" + ] + }, + "moon": { + "meaning": "intuition and cycles", + "emoji": "🌙", + "fortunes": [ + "Trust your intuition; it knows the way!", + "Embrace the cycles of change in your life!", + "Your inner wisdom shines like moonlight!" + ] + }, + "sun": { + "meaning": "vitality and success", + "emoji": "☀️", + "fortunes": [ + "Your energy radiates success and warmth!", + "Bright opportunities are on the horizon!", + "Your light will inspire others today!" + ] + }, + "cloud": { + "meaning": "imagination and possibility", + "emoji": "☁️", + "fortunes": [ + "Your imagination opens doors to new possibilities!", + "Dream big; the sky is not the limit!", + "Creative solutions float into your awareness!" + ] + } + } + + # Get data for chosen options + element_info = element_data[choice1] + time_info = time_data[choice2] + symbol_info = symbol_data[choice3] + + # Generate personalized fortune + base_fortune = rng.choice(symbol_info["fortunes"]) + + # Create combination description + combination = ( + f"You are {element_info['trait']}, seeking {time_info['meaning']}, " + f"guided by {symbol_info['meaning']}." + ) + + # Generate lucky number based on choices (deterministic for same choices) + choice_seed = hash(f"{choice1}{choice2}{choice3}") % 10000 + temp_rng = random.Random(choice_seed) + lucky_num = temp_rng.randint(1, 99) + + # Build full fortune message + full_fortune = ( + f"{element_info['emoji']} {time_info['emoji']} {symbol_info['emoji']} " + f"{base_fortune}" + ) + + return { + "fortune": full_fortune, + "element": choice1, + "time": choice2, + "symbol": choice3, + "combination": combination, + "lucky_number": lucky_num, + "lucky_color": element_info["color"] + } \ No newline at end of file diff --git a/tests/test_core.py b/tests/test_core.py index 6eb2f17..b413fc0 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ import pytest -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color, get_tarot_reading, get_fortune_by_choice def test_get_fortune(): result = get_fortune() @@ -32,3 +32,23 @@ def test_get_tarot_reading_invalid_intent_fallback(): assert isinstance(text, str) assert ":" in text +def test_get_fortune_by_choice_returns_dict(): + """Test that function returns a dictionary.""" + result = get_fortune_by_choice("fire", "dawn", "star") + assert isinstance(result, dict) + +def test_get_fortune_by_choice_has_required_keys(): + """Test that returned dict has all required keys.""" + result = get_fortune_by_choice("fire", "dawn", "star") + required_keys = ["fortune", "element", "time", "symbol", "combination", "lucky_number", "lucky_color"] + for key in required_keys: + assert key in result + +def test_get_fortune_by_choice_valid_elements(): + """Test all valid element choices.""" + elements = ["fire", "water", "earth", "air"] + for element in elements: + result = get_fortune_by_choice(element, "dawn", "star") + assert result["element"] == element + assert isinstance(result["fortune"], str) + assert len(result["fortune"]) > 0 \ No newline at end of file From bb4d45ead27c3e942983aec9221360e215f2a0a9 Mon Sep 17 00:00:00 2001 From: TogawaSaki0214 Date: Mon, 3 Nov 2025 02:43:06 -0500 Subject: [PATCH 08/19] add rune function --- src/pyfortunecookie/core.py | 20 ++++++++++++++++++++ tests/test_core.py | 20 +++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index 019b96c..7630021 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -55,6 +55,17 @@ "mono": ["black", "white", "gray"] } +_RUNES = { + "Fehu": "Wealth, new beginnings, prosperity.", + "Uruz": "Strength and endurance.", + "Thurisaz": "Conflict, challenge, or protection.", + "Ansuz": "Wisdom, communication, divine inspiration.", + "Raidho": "Journey, movement, or progress.", + "Kenaz": "Creativity, revelation, transformation.", + "Gebo": "Gift, partnership, generosity.", + "Wunjo": "Joy, harmony, well-being." +} + def get_fortune(rng: Optional[random.Random] = None) -> str: """Return a random fortune sentence.""" rng = rng or random @@ -93,3 +104,12 @@ def get_tarot_reading(intent: Optional[str] = None, rng: Optional[random.Random] meaning = _TAROT[card] return f"{card}: {meaning}" +def get_rune_reading(n=3, rng: Optional[random.Random] = None) -> str: + """Return a rune reading with n runes.""" + if n < 1: + raise ValueError("n must be at least 1") + rng = rng or random + selected_runes = rng.sample(list(_RUNES.keys()), k=n) + readings = [f"{rune}: {_RUNES[rune]}" for rune in selected_runes] + return readings + diff --git a/tests/test_core.py b/tests/test_core.py index 6eb2f17..8c4ac38 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ import pytest -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading, get_rune_reading def test_get_fortune(): result = get_fortune() @@ -32,3 +32,21 @@ def test_get_tarot_reading_invalid_intent_fallback(): assert isinstance(text, str) assert ":" in text +def test_get_rune_reading_correct_format(): + rng = random.Random(42) + result = get_rune_reading(n=3, rng=rng) + assert isinstance(result, list) + assert len(result) == 3 + assert all(isinstance(r, str) for r in result) + assert all(":" in r for r in result) + +def test_get_rune_reading_uniqueness(): + a = random.Random(100) + b = random.Random(100) + ra = get_rune_reading(n=3, rng=a) + rb = get_rune_reading(n=3, rng=b) + assert ra == rb + +def test_get_rune_reading_invalid_n(): + with pytest.raises(ValueError): + get_rune_reading(n=0) \ No newline at end of file From bcb3e04599b373326ab0b05cc19cde328d527c1c Mon Sep 17 00:00:00 2001 From: amendahawi Date: Mon, 3 Nov 2025 14:41:01 -0500 Subject: [PATCH 09/19] remove unused os.nice import --- src/pyfortunecookie/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index 9b51477..f02d7c2 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -1,4 +1,3 @@ -from os import nice import random from typing import Optional From 92b7356612cd2552f13798ba3e6167774e9d0403 Mon Sep 17 00:00:00 2001 From: TogawaSaki0214 Date: Mon, 3 Nov 2025 15:07:58 -0500 Subject: [PATCH 10/19] add random to fix --- tests/test_core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index a496c74..52eaf30 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ -import pytest -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading, get_rune_reading, get_fortune_by_choice +import pytest, random +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading, get_rune_reading def test_get_fortune(): result = get_fortune() From 4ed161e03a875af59f4b5446bc89b4dd3ebc935d Mon Sep 17 00:00:00 2001 From: TogawaSaki0214 Date: Mon, 3 Nov 2025 15:28:39 -0500 Subject: [PATCH 11/19] import new method in test_core --- tests/test_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_core.py b/tests/test_core.py index 52eaf30..3552490 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,6 @@ import pytest, random -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading, get_rune_reading +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color +from pyfortunecookie.core import get_tarot_reading, get_rune_reading, get_fortune_by_choice def test_get_fortune(): result = get_fortune() From 39ba73b8a19eb6542edeff0d496671ec23fa0e3e Mon Sep 17 00:00:00 2001 From: amendahawi Date: Mon, 3 Nov 2025 15:30:33 -0500 Subject: [PATCH 12/19] implemented new function get_lucky_day with 3 test cases --- src/pyfortunecookie/__init__.py | 4 ++-- src/pyfortunecookie/__main__.py | 7 ++++++- src/pyfortunecookie/core.py | 36 +++++++++++++++++++++++++++++++++ tests/test_core.py | 24 ++++++++++++++++++++-- 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/src/pyfortunecookie/__init__.py b/src/pyfortunecookie/__init__.py index e4b7e50..ff66594 100644 --- a/src/pyfortunecookie/__init__.py +++ b/src/pyfortunecookie/__init__.py @@ -8,6 +8,6 @@ For more information, see the official documentation: https://docs.python.org/3/reference/import.html#regular-packages """ -from .core import get_fortune, get_lucky_number, get_color +from .core import get_fortune, get_lucky_number, get_color, get_lucky_day -__all__ = ["get_fortune", "get_lucky_number", "get_color", "get_tarot_reading", "get_fortune_by_choice"] +__all__ = ["get_fortune", "get_lucky_number", "get_color", "get_tarot_reading", "get_fortune_by_choice", "get_lucky_day"] diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py index e57da59..5849a65 100644 --- a/src/pyfortunecookie/__main__.py +++ b/src/pyfortunecookie/__main__.py @@ -3,13 +3,18 @@ directly from command line, as opposed to importing it into another program. """ -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color,get_tarot_reading, get_fortune_by_choice +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color, get_tarot_reading, get_fortune_by_choice, get_lucky_day def main(): print("🥠 Welcome to PyFortune Cookie!\n") print(f"Today's fortune: {get_fortune()}") print(f"Your lucky number: {get_lucky_number()}") print(f"Your lucky color: {get_color()}") + + # Get lucky day and format it nicely + lucky_day = get_lucky_day() + print(f"Your lucky day: {lucky_day['day']} - {lucky_day['message']}") + print("\n🔮 Tarot Reading:") print(get_tarot_reading()) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index f02d7c2..5cfe50f 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -54,6 +54,16 @@ "mono": ["black", "white", "gray"] } +_LUCKY_DAYS = { + "Friday": "A blessed day awaits you.", + "Saturday": "A day for rest and rejuvenation.", + "Sunday": "A day for reflection and self-care.", + "Monday": "A fresh start to the week brings new opportunities.", + "Tuesday": "Your determination will pay off today.", + "Wednesday": "Communication leads to breakthroughs.", + "Thursday": "Expansion and growth are in your favor.", +} + def get_fortune(rng: Optional[random.Random] = None) -> str: """Return a random fortune sentence.""" rng = rng or random @@ -253,4 +263,30 @@ def get_fortune_by_choice(choice1: str, choice2: str, choice3: str, rng: Optiona "combination": combination, "lucky_number": lucky_num, "lucky_color": element_info["color"] + } + +def get_lucky_day(rng: Optional[random.Random] = None) -> dict: + """ + Return a lucky day of the week with its special meaning. + + Args: + rng: Optional random number generator for testing + + Returns: + Dictionary containing: + - day: The name of the lucky day + - message: A message about what makes this day special + + Example: + >>> result = get_lucky_day() + >>> print(f"{result['day']}: {result['message']}") + "Wednesday: Communication leads to breakthroughs." + """ + rng = rng or random + day = rng.choice(list(_LUCKY_DAYS.keys())) + message = _LUCKY_DAYS[day] + + return { + "day": day, + "message": message } \ No newline at end of file diff --git a/tests/test_core.py b/tests/test_core.py index b413fc0..1e31c70 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -1,5 +1,5 @@ import pytest -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color, get_tarot_reading, get_fortune_by_choice +from pyfortunecookie.core import get_fortune, get_lucky_number, get_color, get_tarot_reading, get_fortune_by_choice, get_lucky_day def test_get_fortune(): result = get_fortune() @@ -51,4 +51,24 @@ def test_get_fortune_by_choice_valid_elements(): result = get_fortune_by_choice(element, "dawn", "star") assert result["element"] == element assert isinstance(result["fortune"], str) - assert len(result["fortune"]) > 0 \ No newline at end of file + assert len(result["fortune"]) > 0 + +def test_get_lucky_day_returns_dict(): + """Test that get_lucky_day returns a dictionary.""" + result = get_lucky_day() + assert isinstance(result, dict) + +def test_get_lucky_day_has_required_keys(): + """Test that the returned dict has all required keys.""" + result = get_lucky_day() + assert "day" in result + assert "message" in result + assert isinstance(result["day"], str) + assert isinstance(result["message"], str) + +def test_get_lucky_day_valid_days(): + """Test that the day returned is a valid day of the week.""" + valid_days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] + result = get_lucky_day() + assert result["day"] in valid_days + assert len(result["message"]) > 0 \ No newline at end of file From 09757f132e9b927fa9b34afe42ddf5b043b8c429 Mon Sep 17 00:00:00 2001 From: amendahawi Date: Mon, 3 Nov 2025 15:33:41 -0500 Subject: [PATCH 13/19] refactor: update formatting and docstring for lucky day feature --- src/pyfortunecookie/__main__.py | 2 +- src/pyfortunecookie/core.py | 14 +++----------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py index 5849a65..a77e8d4 100644 --- a/src/pyfortunecookie/__main__.py +++ b/src/pyfortunecookie/__main__.py @@ -13,7 +13,7 @@ def main(): # Get lucky day and format it nicely lucky_day = get_lucky_day() - print(f"Your lucky day: {lucky_day['day']} - {lucky_day['message']}") + print(f"Your lucky day: {lucky_day['day']}: {lucky_day['message']}") print("\n🔮 Tarot Reading:") print(get_tarot_reading()) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index 5cfe50f..5bf626c 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -56,8 +56,8 @@ _LUCKY_DAYS = { "Friday": "A blessed day awaits you.", - "Saturday": "A day for rest and rejuvenation.", - "Sunday": "A day for reflection and self-care.", + "Saturday": "A day of rest and rejuvenation.", + "Sunday": "A day of reflection and self-care.", "Monday": "A fresh start to the week brings new opportunities.", "Tuesday": "Your determination will pay off today.", "Wednesday": "Communication leads to breakthroughs.", @@ -267,20 +267,12 @@ def get_fortune_by_choice(choice1: str, choice2: str, choice3: str, rng: Optiona def get_lucky_day(rng: Optional[random.Random] = None) -> dict: """ - Return a lucky day of the week with its special meaning. - - Args: - rng: Optional random number generator for testing + Return a lucky day of the week and its lucky meaning. Returns: Dictionary containing: - day: The name of the lucky day - message: A message about what makes this day special - - Example: - >>> result = get_lucky_day() - >>> print(f"{result['day']}: {result['message']}") - "Wednesday: Communication leads to breakthroughs." """ rng = rng or random day = rng.choice(list(_LUCKY_DAYS.keys())) From 5264cc0795384679281bf216e522d9de1d9c32c5 Mon Sep 17 00:00:00 2001 From: amendahawi Date: Mon, 3 Nov 2025 15:53:54 -0500 Subject: [PATCH 14/19] syntax error --- src/pyfortunecookie/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index c54572d..5999b75 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -62,7 +62,8 @@ "Tuesday": "Your determination will pay off today.", "Wednesday": "Communication leads to breakthroughs.", "Thursday": "Expansion and growth are in your favor.", - +} + _RUNES = { "Fehu": "Wealth, new beginnings, prosperity.", "Uruz": "Strength and endurance.", From 87a07e5bc7998a63a2404d889884117a786ad68b Mon Sep 17 00:00:00 2001 From: SinaL0123 Date: Tue, 4 Nov 2025 15:25:03 -0500 Subject: [PATCH 15/19] Add zodiac+MBTI feature with tests --- instructions.md | 62 ++++++++++ pyproject.toml | 10 +- src/pyfortunecookie/__main__.py | 197 ++++++++++++++++++++++++-------- src/pyfortunecookie/core.py | 66 +++++++++++ tests/test_core.py | 48 ++++++++ 5 files changed, 329 insertions(+), 54 deletions(-) create mode 100644 instructions.md diff --git a/instructions.md b/instructions.md new file mode 100644 index 0000000..dc03e03 --- /dev/null +++ b/instructions.md @@ -0,0 +1,62 @@ +# Python Package Exercise + +A little exercise to create a Python package, build it, test it, distribute it, and use it. + +## Concept + +A _package_ is software that has been built, tested, and distributed in a way that makes it easy for other developers to download, install, and configure to add extra functionality to their own programs. Your job is to create a Python package that brings a little bit of joy and levity to the lives of other developers. The package you create should be lighthearted and not "serious" software. However, you will create it following _rigorous_ software engineering practices. + +## Inspiration + +Some inspirational Python packages, for example: + +- [pyjokes](https://pypi.org/project/pyjokes/) +- [pyfiglet](https://github.com/pwaller/pyfiglet) +- [pycowsay](https://pypi.org/project/pycowsay/) +- [emoji](https://pypi.org/project/emoji/) +- [python-lorem](https://pypi.org/project/python-lorem/) +- [horoscope](https://pypi.org/project/horoscope/) +- [freegames](https://pypi.org/project/freegames/) + +## Requirements + +Create a Python package with at least **four functions that accept arguments** which influence their behavior. The package must be distributed in the [PyPI](https://pypi.org/) repository and installable via [pip](https://pypi.org/project/pip/). + +- Use [pipenv](https://packaging.python.org/en/latest/tutorials/managing-dependencies/) to manage the package dependencies and virtual environments with a `Pipfile`. +- Use [pytest](https://docs.pytest.org/en/latest/) to write and run tests to validate that your package code behaves as expected. Create as many tests as necessary to thorooughly verify each function's expected behavior - this should be no fewer than three tests per package function. +- Use [build](https://pypa-build.readthedocs.io/en/stable/index.html) to create the package artifacts. +- Use [twine](https://pypi.org/project/twine/) to upload the package to PyPI. +- Use [GitHub Actions](https://github.com/actions) to build your package and run your tests on two different recent versions of Python with every pull request to the `main` branch of your GitHub repository. + +Create an example program that uses all functions of your package and demonstrates its complete functionality. + +## Developer workflow + +All team members must have visibly contributed to the code using their own git & GitHub accounts in order to claim that they contributed to the project. + +All code changes must be done in feature branches and not directly in the `main` branch. + +To merge code from a feature branch into the `main` branch, do the following: + +1. Create a pull request from the feature branch to the `main` branch. +1. Ask a fellow developer to review your code. +1. The reviewer must review the code and run unit tests to verify that the functions behave as expepcted. +1. If the reviewer has any concerns, discuss then and make any changes agreed upon. +1. Merge the pull request into the `main` branch. +1. Delete the feature branch. +1. Pull the latest changes from the remote `main` branch to your local `main` branch. + +**Warning**: the longer you let code sit in a feature branch, the more likely your team is to end up in [merge hell](https://en.wikipedia.org/wiki/Merge_hell). . Merge feature branches into `main` often to avoid this fate. + +## Documentation + +Replace the contents of the [README.md](./README.md) file with a beautifully-formatted Markdown file including a plain-language **description** of your project and **clear instructions**, including exact **code examples**, for: + +- how a developer who wants to import your project into their own code can do so - include documentation for all functions in your package and a link to an example Python program that uses each of them. +- how a developer who wants to contribute to your project can set up the virtual environment, install dependencies, and build and test your package for themselves. + +Include a [badge](https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/adding-a-workflow-status-badge) at the top of the `README.md` file showing the result of the latest build/test workflow run. + +Include the names of all teammates as links to their GitHub profiles in the README.md file. + +Include a link to your package's page on the PyPI website. diff --git a/pyproject.toml b/pyproject.toml index 219dd93..dfe7994 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,13 +5,13 @@ build-backend = "setuptools.build_meta" [project] name = "pyfortunecookie" description = "A fun fortune cookie generator with lucky number and color" -version = "0.1.1" +version = "0.1.2" authors = [ { name="SinaL0123", email="sl9608@nyu.edu" }, - { name = "DplayerXAX" }, - { name = "aayanmathur" }, - { name = "TogawaSaki0214" }, - { name = "TickyTacky" } + { name = "DplayerXAX", email="danielhuang0905@outlook.com" }, + { name = "aayanmathur", email="am12611@nyu.edu"}, + { name = "TogawaSaki0214", email="am12611@nyu.edu" }, + { name = "TickyTacky", email="am12611@nyu.edu" } ] license = { file = "LICENSE" } readme = "README.md" diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py index a77e8d4..307d5e3 100644 --- a/src/pyfortunecookie/__main__.py +++ b/src/pyfortunecookie/__main__.py @@ -1,62 +1,161 @@ -""" -In Python packages, this file called __main__.py is run when the package is run -directly from command line, as opposed to importing it into another program. -""" +# Categories: +# 1 = Zodiac & MBTI Summary - Written By Sina +# 2 = Tarot Reading +# 3 = Personalized Fortune -from pyfortunecookie.core import get_fortune, get_lucky_number, get_color, get_tarot_reading, get_fortune_by_choice, get_lucky_day +from __future__ import annotations +import argparse +import re +from typing import Optional -def main(): - print("🥠 Welcome to PyFortune Cookie!\n") - print(f"Today's fortune: {get_fortune()}") - print(f"Your lucky number: {get_lucky_number()}") - print(f"Your lucky color: {get_color()}") - - # Get lucky day and format it nicely - lucky_day = get_lucky_day() - print(f"Your lucky day: {lucky_day['day']}: {lucky_day['message']}") - - print("\n🔮 Tarot Reading:") - print(get_tarot_reading()) - - # Interactive personalized fortune - print("\n🎴 Personalized Fortune (by your choices):") - - # Ask user for their choices - elements = ["fire", "water", "earth", "air"] - times = ["dawn", "noon", "dusk", "midnight"] - symbols = ["star", "moon", "sun", "cloud"] +from .core import ( + get_zodiac_mbti_summary, + get_tarot_reading, + get_fortune_by_choice, + is_valid_zodiac, + is_valid_mbti +) - while True: - choice1 = input(f"Choose an element {elements}: ").strip().lower() - if choice1 in elements: - break - print(f"Invalid choice. Please choose one of {elements}.") +# ---- helpers ---- +def parse_category_from_attribute(attr: str) -> Optional[int]: + """Parse strings like 'fortune(category=1)' → 1.""" + if not attr: + return None + m = re.search(r"category\s*=\s*(\d+)", attr) + if m: + try: + return int(m.group(1)) + except ValueError: + return None + return None +def normalize_category(cat: Optional[str | int]) -> Optional[int]: + """Allow 1/2/3 or names: astro|tarot|personal.""" + if cat is None: + return None + if isinstance(cat, int): + return cat if cat in (1, 2, 3) else None + s = str(cat).strip().lower() + if s in {"1", "astro", "astrology", "zodiac"}: + return 1 + if s in {"2", "tarot"}: + return 2 + if s in {"3", "personal", "personalized"}: + return 3 + return None + +def ask_choice(label: str, options: list[str]) -> str: while True: - choice2 = input(f"Choose a time {times}: ").strip().lower() - if choice2 in times: - break - print(f"Invalid choice. Please choose one of {times}.") + ans = input(f"{label} {options}: ").strip().lower() + if ans in options: + return ans + print(f"Invalid choice. Please choose one of {options}.") + +def main(): + parser = argparse.ArgumentParser( + description="PyFortune Cookie CLI (choose a category to run)." + ) + parser.add_argument("--category", type=str, default=None, + help="1|2|3 or names: astro|tarot|personal") + parser.add_argument("--attribute", type=str, default=None, + help='Alternative selector text, e.g. "fortune(category=1)"') + parser.add_argument("--zodiac", type=str, default=None, + help="Your zodiac (e.g., aries, libra, pisces) for category 1") + parser.add_argument("--mbti", type=str, default=None, + help="Your MBTI (e.g., INFP, ESTJ) for category 1") + parser.add_argument("--no-input", action="store_true", + help="Skip zodiac/MBTI prompts for category 1") + args = parser.parse_args() + + print("🥠 Welcome to PyFortune Cookie!") while True: - choice3 = input(f"Choose a symbol {symbols}: ").strip().lower() - if choice3 in symbols: - break - print(f"Invalid choice. Please choose one of {symbols}.") + cat: Optional[int] = normalize_category(args.category) + if cat is None and args.attribute: + cat = parse_category_from_attribute(args.attribute) + if cat is None: + print("\nSelect a category:") + print(" 1) Zodiac & MBTI Summary") + print(" 2) Tarot Reading") + print(" 3) Personalized Fortune") + while True: + raw = input("Enter 1 / 2 / 3: ").strip() + if raw in {"1", "2", "3"}: + cat = int(raw) + break + print("Please enter 1, 2, or 3.") + + # Run category + if cat == 1: + # Zodiac & MBTI Summary + zodiac = args.zodiac + mbti = args.mbti - # Generate personalized fortune - choice_result = get_fortune_by_choice(choice1, choice2, choice3) + if not args.no_input and (zodiac is None and mbti is None): + # zodiac: loop until valid or skipped + while True: + z = input("Enter your zodiac (e.g., Aries, Libra, Pisces) or press Enter to skip: ").strip() + if z == "" or is_valid_zodiac(z): + zodiac = z or None + break + print("Not a valid zodiac. Please try again (e.g., Aries, Libra).") - # Print results - print("\n✨ Your Personalized Fortune ✨") - print(f"Element: {choice_result['element']}") - print(f"Time: {choice_result['time']}") - print(f"Symbol: {choice_result['symbol']}") - print(f"Combination: {choice_result['combination']}") - print(f"Fortune: {choice_result['fortune']}") - print(f"Lucky Number: {choice_result['lucky_number']}") - print(f"Lucky Color: {choice_result['lucky_color']}") + # mbti: loop until valid or skipped + while True: + m = input("Enter your MBTI (e.g., INFP, ESTJ) or press Enter to skip: ").strip() + if m == "" or is_valid_mbti(m): + mbti = m or None + break + print("Not a valid MBTI. Please try again (e.g., INFP, ESTJ).") + summary = get_zodiac_mbti_summary(zodiac=zodiac, mbti=mbti) + + print("\n🌙 Your Fortune Summary 🌙") + if summary["zodiac"] or summary["mbti"]: + print(f"Zodiac: {summary['zodiac'].title() if summary['zodiac'] else '-'} | MBTI: {summary['mbti'] or '-'}") + print(f"Fortune: {summary['fortune']}") + print(f"Lucky Number: {summary['lucky_number']}") + print(f"Lucky Color: {summary['lucky_color']}") + print(f"Lucky Day: {summary['lucky_day']['day']} - {summary['lucky_day']['message']}") + + elif cat == 2: + # Tarot Reading + print("\n🔮 Tarot Reading:") + print(get_tarot_reading()) + + elif cat == 3: + # Personalized Fortune + print("\n🎴 Personalized Fortune (by your choices):") + elements = ["fire", "water", "earth", "air"] + times = ["dawn", "noon", "dusk", "midnight"] + symbols = ["star", "moon", "sun", "cloud"] + + c1 = ask_choice("Choose an element", elements) + c2 = ask_choice("Choose a time", times) + c3 = ask_choice("Choose a symbol", symbols) + + result = get_fortune_by_choice(c1, c2, c3) + + print("\n✨ Your Personalized Fortune ✨") + print(f"Element: {result['element']}") + print(f"Time: {result['time']}") + print(f"Symbol: {result['symbol']}") + print(f"Combination: {result['combination']}") + print(f"Fortune: {result['fortune']}") + + else: + print("Unknown category. Please choose 1, 2, or 3.") + + # Ask if user wants to continue / exit + again = input("\nWould you like to choose another category? (y/n): ").strip().lower() + if again not in {"y", "yes"}: + # exit + print("\n🌟 Thank you for using PyFortune Cookie! Goodbye! 🌟") + break + else: + #continue + args.category = None + args.attribute = None if __name__ == "__main__": main() diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index 5999b75..6bb87e9 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -94,6 +94,72 @@ def get_color(palette: str = "soft", rng: Optional[random.Random] = None) -> str rng = rng or random return rng.choice(_PALETTES[palette]) +# fortune summary influenced by zodiac/MBTI. +def is_valid_zodiac(z: str | None) -> bool: + """Check if zodiac string is one of 12 Western zodiac signs.""" + if not z: + return False + z = z.strip().lower() + return z in { + "aries","taurus","gemini","cancer","leo","virgo", + "libra","scorpio","sagittarius","capricorn","aquarius","pisces" + } + +def is_valid_mbti(m: str | None) -> bool: + """Check if MBTI is one of the 16 types.""" + if not m: + return False + m = m.strip().upper() + return m in { + "INTJ","INTP","ENTJ","ENTP","INFJ","INFP","ENFJ","ENFP", + "ISTJ","ISFJ","ESTJ","ESFJ","ISTP","ISFP","ESTP","ESFP" + } + +def get_zodiac_mbti_summary(zodiac: Optional[str] = None, + mbti: Optional[str] = None, + rng: Optional[random.Random] = None) -> dict: + + rng = rng or random + + # normalize inputs + z = (zodiac or "").strip().lower() + m = (mbti or "").strip().upper() + + # zodiac -> palette preference (only affects color choice) + palette_map = { + "aries": "bold", "leo": "bold", "sagittarius": "bold", + "taurus": "mono", "virgo": "mono", "capricorn": "mono", + "gemini": "soft", "libra": "soft", "aquarius": "soft", + "cancer": "soft", "scorpio": "bold", "pisces": "soft", + } + palette = palette_map.get(z, "soft") + + # MBTI tilt on fortune tone + prefer_action = ("T" in m) or ("J" in m) + prefer_idea = "N" in m + + sentence = get_fortune(rng=rng) + if prefer_action: + sentence += " Take action with confidence!" + elif prefer_idea: + sentence += " Let your imagination guide you!" + + # lucky color/number/day + color = get_color(palette=palette, rng=rng) + number = get_lucky_number() + day = get_lucky_day(rng=rng) + + return { + "fortune": sentence, + "lucky_color": color, + "lucky_number": number, + "lucky_day": day, + "zodiac": z or None, + "mbti": m or None, + "palette_used": palette, + } + + def get_tarot_reading(intent: Optional[str] = None, rng: Optional[random.Random] = None) -> str: """ Return a tarot reading with slight bias based on intent. diff --git a/tests/test_core.py b/tests/test_core.py index 8c794b7..d9bccf9 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -91,3 +91,51 @@ def test_get_lucky_day_valid_days(): result = get_lucky_day() assert result["day"] in valid_days assert len(result["message"]) > 0 + +# tests for zodiac + MBTI fortune +from pyfortunecookie.core import ( + get_zodiac_mbti_summary, + is_valid_zodiac, + is_valid_mbti, +) + +def test_personality_summary_structure_and_types(): + s = get_zodiac_mbti_summary(zodiac=None, mbti=None) + assert isinstance(s, dict) + # required keys + for k in ["fortune", "lucky_color", "lucky_number", "lucky_day"]: + assert k in s + # types + assert isinstance(s["fortune"], str) + assert isinstance(s["lucky_color"], str) + assert isinstance(s["lucky_number"], int) + assert isinstance(s["lucky_day"], dict) + assert "day" in s["lucky_day"] and "message" in s["lucky_day"] + +def test_mbti_tilt_messages(): + """INTJ biases to action message; INFP biases to imagination message.""" + s_action = get_zodiac_mbti_summary(zodiac="Libra", mbti="INTJ") + assert "Take action with confidence!" in s_action["fortune"] + + s_imagination = get_zodiac_mbti_summary(zodiac="Libra", mbti="INFP") + assert "Let your imagination guide you!" in s_imagination["fortune"] + +def test_validators_accept_valid_and_reject_invalid(): + """Validators accept real values and reject typos like 'entl'.""" + # zodiac + assert is_valid_zodiac("Aries") + assert is_valid_zodiac("pisces") + assert not is_valid_zodiac("dragon") + assert not is_valid_zodiac("") + # mbti + assert is_valid_mbti("ENTP") + assert is_valid_mbti("infj") + assert not is_valid_mbti("entl") # typo should be rejected + assert not is_valid_mbti("abcd") + +def test_color_present_with_zodiac(): + """Smoke test: with a zodiac provided, summary still returns a non-empty color.""" + s = get_zodiac_mbti_summary(zodiac="Aries", mbti=None) + assert isinstance(s["lucky_color"], str) + assert len(s["lucky_color"]) > 0 + From d613b63b6cce30eccfd54a987c9fd551acc58379 Mon Sep 17 00:00:00 2001 From: SinaL0123 Date: Tue, 4 Nov 2025 16:01:54 -0500 Subject: [PATCH 16/19] fix bugs for Python3.9ver --- src/pyfortunecookie/__main__.py | 4 ++-- src/pyfortunecookie/core.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pyfortunecookie/__main__.py b/src/pyfortunecookie/__main__.py index 307d5e3..96a7f7f 100644 --- a/src/pyfortunecookie/__main__.py +++ b/src/pyfortunecookie/__main__.py @@ -6,7 +6,7 @@ from __future__ import annotations import argparse import re -from typing import Optional +from typing import Optional, Union from .core import ( get_zodiac_mbti_summary, @@ -29,7 +29,7 @@ def parse_category_from_attribute(attr: str) -> Optional[int]: return None return None -def normalize_category(cat: Optional[str | int]) -> Optional[int]: +def normalize_category(cat: Optional[Union[str, int]]) -> Optional[int]: """Allow 1/2/3 or names: astro|tarot|personal.""" if cat is None: return None diff --git a/src/pyfortunecookie/core.py b/src/pyfortunecookie/core.py index 6bb87e9..0d9e965 100644 --- a/src/pyfortunecookie/core.py +++ b/src/pyfortunecookie/core.py @@ -95,7 +95,7 @@ def get_color(palette: str = "soft", rng: Optional[random.Random] = None) -> str return rng.choice(_PALETTES[palette]) # fortune summary influenced by zodiac/MBTI. -def is_valid_zodiac(z: str | None) -> bool: +def is_valid_zodiac(z: Optional[str]) -> bool: """Check if zodiac string is one of 12 Western zodiac signs.""" if not z: return False @@ -105,7 +105,7 @@ def is_valid_zodiac(z: str | None) -> bool: "libra","scorpio","sagittarius","capricorn","aquarius","pisces" } -def is_valid_mbti(m: str | None) -> bool: +def is_valid_mbti(m: Optional[str]) -> bool: """Check if MBTI is one of the 16 types.""" if not m: return False From de5271e15530a90ede954880c4dff18c224d1214 Mon Sep 17 00:00:00 2001 From: amendahawi Date: Tue, 4 Nov 2025 16:22:10 -0500 Subject: [PATCH 17/19] update amendahawi name and email --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dfe7994..dc225fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ { name = "DplayerXAX", email="danielhuang0905@outlook.com" }, { name = "aayanmathur", email="am12611@nyu.edu"}, { name = "TogawaSaki0214", email="am12611@nyu.edu" }, - { name = "TickyTacky", email="am12611@nyu.edu" } + { name = "amendahawi", email="am13948@nyu.edu" } ] license = { file = "LICENSE" } readme = "README.md" From 25396c1ab89737a9af06831af7243ef212fd46d2 Mon Sep 17 00:00:00 2001 From: amendahawi Date: Tue, 4 Nov 2025 16:38:42 -0500 Subject: [PATCH 18/19] updated read me with pypi link, badge build, and team members --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f9ed7bd..405f777 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,16 @@ +[![CI / CD](https://github.com/swe-students-fall2025/3-python-package-team_meridian/actions/workflows/build.yaml/badge.svg)](https://github.com/swe-students-fall2025/3-python-package-team_meridian/actions/workflows/build.yaml) +PyPI: https://pypi.org/project/PyFortuneCookie/ + # PyFortuneCookie -A fun Python package that generates your **daily fortune cookie** — complete with a lucky number and color! -Perfect for learning how to build and run Python packages. +PyFortuneCookie is a Python package that generates your **daily fortune cookie** — complete with a lucky number, color and day! + +## Team Members +[Sina Liu](https://github.com/SinaL0123) +[Aayan Mathur](https://github.com/aayanmathur) +[Daniel Huang](https://github.com/DplayerXAX) +[Togawa Saki](https://github.com/TogawaSaki0214) +[Abdul Mendahawi](https://github.com/amendahawi) --- From bf3fd370eabd07d279307fcad75b5c5216dcc721 Mon Sep 17 00:00:00 2001 From: amendahawi Date: Tue, 4 Nov 2025 16:45:15 -0500 Subject: [PATCH 19/19] fixed readme spacing --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 405f777..35c74cd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ [![CI / CD](https://github.com/swe-students-fall2025/3-python-package-team_meridian/actions/workflows/build.yaml/badge.svg)](https://github.com/swe-students-fall2025/3-python-package-team_meridian/actions/workflows/build.yaml) + PyPI: https://pypi.org/project/PyFortuneCookie/ # PyFortuneCookie @@ -7,9 +8,13 @@ PyFortuneCookie is a Python package that generates your **daily fortune cookie** ## Team Members [Sina Liu](https://github.com/SinaL0123) + [Aayan Mathur](https://github.com/aayanmathur) + [Daniel Huang](https://github.com/DplayerXAX) + [Togawa Saki](https://github.com/TogawaSaki0214) + [Abdul Mendahawi](https://github.com/amendahawi) ---