Repository: unitedstates/python-us (language: python)

Issue:
Jellyfish 1.x
This initial PR bumps jellyfish to 1.x, regenerates the lockfile, fixes CI, and adds the birthday constant. This is the first of the changes dug out from the big branch.


Discussion / hints:
@jcarbaugh @pauldhawk pretty please 😇
+1 on this
+1 thank you!
+1 Thank you for doing this!
@jcarbaugh @pauldhawk @dgilmanAIDENTIFIED hey guys! any chance we can get this approved? It's blocking upgrading Python 3.11 -> 3.12 in projects which this depends on.
I've been a bit busy lately but have plans soon / this weekend to review this project and get my changes merged one PR at a time. (I think the big one needed some more careful review). I've been added to this repo but I unfortunately do not yet have access to PyPI, however, once the repo is caught up I can ask again, or at the very least deploy a temporary fork.

Fix patch:
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 99a47ee..1bb98b6 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -1,36 +1,69 @@
 name: Tests
 
-on: [push]
+on: [pull_request]
 
 jobs:
-  build:
+  linting:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set Python environment
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+          architecture: x64
+
+      - name: Install dependencies
+        run: pip install -e .[dev]
+
+      - name: Linting
+        run: |
+          flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+          flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
 
+      - name: Check code format with Black
+        run: black --check us
+
+  testing:
+    needs: linting
     runs-on: ubuntu-latest
     strategy:
-      max-parallel: 4
+      max-parallel: 6
       matrix:
-        python-version: [3.8, 3.9, "3.10", 3.11, 3.12]
-
+        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
     steps:
     - uses: actions/checkout@v4
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v4
+      uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
-    - name: Install pipenv
-      uses: dschep/install-pipenv-action@v1
     - name: Install dependencies
       run: |
-        pipenv install --dev --skip-lock --python `which python`
-    - name: Linting and formatting
-      if: ${{ matrix.python-version == '3.11' }}
-      run: |
-        # stop the build if there are Python syntax errors or undefined names
-        pipenv run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
-        # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
-        pipenv run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
-        # check formatting
-        pipenv run black --check us
+        pip install -e .[dev]
     - name: Test with pytest
       run: |
-        pipenv run pytest
+        pytest .
+
+  building:
+    needs: linting
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set Python environment
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.11
+          architecture: x64
+
+      - name: Install dependencies
+        run: |
+          pip install setuptools build wheel
+
+      - name: Builds package
+        run: |
+          python -m build . -w -n
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: wheels
+          path: ./dist/*.whl
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index a6d46ea..e77e5ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
 .env
 
 # Compilation and caching
+build/
+dist/
 .cache/
 .mypy_cache/
 .pytest_cache/
diff --git a/Pipfile b/Pipfile
deleted file mode 100644
index ce241bd..0000000
--- a/Pipfile
+++ /dev/null
@@ -1,19 +0,0 @@
-[[source]]
-name = "pypi"
-url = "https://pypi.org/simple"
-verify_ssl = true
-
-[dev-packages]
-black = "*"
-flake8 = "*"
-importlib_metadata = "*"
-pytest = "*"
-pytz = "*"
-requests = "<3.0"
-
-[packages]
-jellyfish = "~=1.0"
-
-
-[pipenv]
-allow_prereleases = true
diff --git a/Pipfile.lock b/Pipfile.lock
deleted file mode 100644
index 447e2ba..0000000
--- a/Pipfile.lock
+++ /dev/null
@@ -1,352 +0,0 @@
-{
-    "_meta": {
-        "hash": {
-            "sha256": "25af858beb779541c3333a0b439409e7cad654f2bf96ccd1a843fb597cf29887"
-        },
-        "pipfile-spec": 6,
-        "requires": {},
-        "sources": [
-            {
-                "name": "pypi",
-                "url": "https://pypi.org/simple",
-                "verify_ssl": true
-            }
-        ]
-    },
-    "default": {
-        "jellyfish": {
-            "hashes": [
-                "sha256:0433e99e94b6e13bc8af3236c6a80ccf70d75fd25eecb692022aa7f3efb3c0ec",
-                "sha256:05d89204cfb45ca0fb0a5e8eb8a74ed11440dce22c40bf48568b6e5128cbda34",
-                "sha256:067c0360576542f48dec9c61575e594589be22614a453085ed5f393a934085ac",
-                "sha256:08bb36564b076a063c61a531fac1b5e2b07ecab9873df4d67c3d72460c79b127",
-                "sha256:0b0051d2105ff76a87950cd8604b3f964fef4e6d0b83f9832e920835826436c7",
-                "sha256:0fb0a463fc40d6b885f5cfcdb1638d6b096a56053e02819db37f16121ae831e6",
-                "sha256:29a736569aec9e2c96f7e3b3ba16f4309f538867c947a436f1f0a7932fe6cb46",
-                "sha256:2da658184d12a9a1ee1173f3c253ff4e8add8846cbc98e0d3d97ab36033db902",
-                "sha256:319b72b1cc43835010ddb8184f0d06eeda5520a815ec6848dbf1ca74a897e734",
-                "sha256:31b3def6f80ec1c63a91bf14e3083bf7f081ef1661dfaa5a91d29aafc9a519f6",
-                "sha256:3e9f74a3c85951f753be932a6eb3b226436dcfaa655893b6030cd5f49f09096a",
-                "sha256:44329a8a7687d0d8fb6f4389f78b4d6a7cf8e3b683ff5e0e0ce10bb260c0a5fa",
-                "sha256:47190758ed11de7206fe7eae4ddb51616ad3e5ffb624dd90b96fe29d179383a1",
-                "sha256:483c4655ef908b1b5c2c52d97c839f383d4ed3471553b1e9c6f7337b5875f896",
-                "sha256:48483138cdbac8b06250ac36e5d27dc7145511ebec3c9232e3d56e676f69415d",
-                "sha256:486117a0158263ecec9ea54071c25819253bd4ec0d325752b2dc5830b04c7d0e",
-                "sha256:4dfb477ecc9b5b878a4f7f8d8e6a932d73ddf10b2c0fcbbe3a7d8d40426f0297",
-                "sha256:56ae9c53ced7c26917cd9de346688baff7303898d3917355d920de15d7aedea4",
-                "sha256:56ed38d549bb59718f8b5e17645e514d6bf8c633d72f70839fee1eab1484b630",
-                "sha256:57b2e7c452581450c3550373393714ebcf5c2dc445d5744ec74846648c140aee",
-                "sha256:595a74dcd06814b0e62e7a5d3c6a349f5e552697f50549bbd49a86f7ea093b80",
-                "sha256:5d8db82b194768ca82b74d860ed5739a70737b96c572735e25bea00ef00e01f8",
-                "sha256:5e916c119e32ac66abe18d9112d2a31b439dff7125b648671e4bf12f46b04205",
-                "sha256:5f1fbe4a3ee9f0a76ab88a269e2577ef7815fcaa5960e6a2015b50fcbdee5676",
-                "sha256:652f5a3526132ea092209402d28274b1027777aeb4257fd87a3b7fca5832bae3",
-                "sha256:69f6b5f89383705834877b86100cf5a49c985a200076524b1c24ceb9e25916e5",
-                "sha256:6be50a09ca528af4c3d197160eb90fe68cc1e76dff316770f0dc092b1ce8543b",
-                "sha256:74d766394d7148a3cd177cb046592b6e2f3c9ee6a245791bd5454369e6dc7f8a",
-                "sha256:77e827f06861ddabeb147f352ef3848e686666098d76b2e41466a7d8d84c9606",
-                "sha256:78334b1f8240a6a6f09efa8255e83ff5e8d2318e7e3e922f9bbfb3f5256d9404",
-                "sha256:784fcc444d74a13595b7f9e18989470d2f8456b1d5ba95a82b2099d7fe0a0918",
-                "sha256:7aae0205a3d2d20f4175139126c2e7c79b541fa14daab264508b4308546d779f",
-                "sha256:7b7a471e00c7410362617363fc59d534e400aed40b6b740580d3e7a852e91571",
-                "sha256:87a9755a393f9233d7a9afe6c5bd5b5c1910c3c2832ba8562bc4a694b481408a",
-                "sha256:8e6826e9274a790504e252c94e0c21c79b82398b837a3a3275d5528d3b6144ee",
-                "sha256:960b621e234abbf9c612448ce4c99055fd71c7ff8ae722bdae28d927fbe1a59e",
-                "sha256:9a15a391e21e0d7055de04720c95fbbb5ae184a3c87ef079688d456ce51d3cef",
-                "sha256:9a5a8fa58e70ad8e9c112fd09f7f9c3581a7fe3b5dba5a4094aaaa91125f854d",
-                "sha256:ac72ccbb6798816f953e0b4f2452db59d560c0e74be36c58524246861b2b1978",
-                "sha256:ad21cf68d3c7989cb98c40a41e200a00bf03097550c44f87488e7db61b8341e6",
-                "sha256:b26c2bc4cdd6aaaae74b3aa63f2c9c60ac1b07e89d499732698e12f3aa7fc434",
-                "sha256:bbf7bc3674e616f1dee4430a78faf6c6d6b2eb47d86deed834cd93668a1a7cb4",
-                "sha256:bdb20cb00f7a2051f46676625cc0a00cb2395c5eb31aa462e3f3f1706a07d5d7",
-                "sha256:c7a50dbcbf36326bfd93b40b967978c9628b9089f9c278d0b77a37db600815a8",
-                "sha256:c8b3ad146420556cb1286b9b96d3cf628e59ca4639ba6a060b0934c16f2376b1",
-                "sha256:cb398256f4bf2ae5604266f22783070d02e920da2c7f7dff52a6f21ea332f266",
-                "sha256:cf4757b15e257b44d0942ec9c1582b793b7e2e8f4c499a73ed196ad08cdffef8",
-                "sha256:d4000d6d4cf5abf1fbd354948be4647a593864a542dc6c3c5622af63465af7d8",
-                "sha256:dde61fc6f8927064acf2b8699207baa1294f60b517e61f523617e78d5c957cba",
-                "sha256:ef9e13b75382fe504d77e48c13adb963f27206ef93f74d3bc634bef70afb3427",
-                "sha256:f388a539fb3c4742f2bc96d014abc208e4b4ccaa36e4778c8ac03c04e45b5be4",
-                "sha256:f664520189e3ae16e0b5fee21f19d15c8926e2320fd195ccc65d96390d9e9689",
-                "sha256:f83a3d42808d682f74619f63afe23284e3d80f9fc4912ed868b21671c9d124b2",
-                "sha256:fd50e1bff0ada0faa3d76c844c91ea7159737c689f1afbb32a3e0e767d5f334d"
-            ],
-            "index": "pypi",
-            "version": "==1.0.1"
-        }
-    },
-    "develop": {
-        "black": {
-            "hashes": [
-                "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884",
-                "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916",
-                "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258",
-                "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1",
-                "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce",
-                "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d",
-                "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982",
-                "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7",
-                "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173",
-                "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9",
-                "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb",
-                "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad",
-                "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc",
-                "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0",
-                "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a",
-                "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe",
-                "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace",
-                "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"
-            ],
-            "index": "pypi",
-            "version": "==23.10.1"
-        },
-        "certifi": {
-            "hashes": [
-                "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082",
-                "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==2023.7.22"
-        },
-        "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"
-        },
-        "click": {
-            "hashes": [
-                "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
-                "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==8.1.7"
-        },
-        "flake8": {
-            "hashes": [
-                "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23",
-                "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"
-            ],
-            "index": "pypi",
-            "version": "==6.1.0"
-        },
-        "idna": {
-            "hashes": [
-                "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
-                "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==3.4"
-        },
-        "importlib-metadata": {
-            "hashes": [
-                "sha256:3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb",
-                "sha256:dbace7892d8c0c4ac1ad096662232f831d4e64f4c4545bd53016a3e9d4654743"
-            ],
-            "index": "pypi",
-            "version": "==6.8.0"
-        },
-        "iniconfig": {
-            "hashes": [
-                "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
-                "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.0.0"
-        },
-        "mccabe": {
-            "hashes": [
-                "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
-                "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
-            ],
-            "markers": "python_version >= '3.6'",
-            "version": "==0.7.0"
-        },
-        "mypy-extensions": {
-            "hashes": [
-                "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
-                "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
-            ],
-            "markers": "python_version >= '3.5'",
-            "version": "==1.0.0"
-        },
-        "packaging": {
-            "hashes": [
-                "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5",
-                "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==23.2"
-        },
-        "pathspec": {
-            "hashes": [
-                "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20",
-                "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==0.11.2"
-        },
-        "platformdirs": {
-            "hashes": [
-                "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3",
-                "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==3.11.0"
-        },
-        "pluggy": {
-            "hashes": [
-                "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12",
-                "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==1.3.0"
-        },
-        "pycodestyle": {
-            "hashes": [
-                "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f",
-                "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==2.11.1"
-        },
-        "pyflakes": {
-            "hashes": [
-                "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774",
-                "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==3.1.0"
-        },
-        "pytest": {
-            "hashes": [
-                "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac",
-                "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"
-            ],
-            "index": "pypi",
-            "version": "==7.4.3"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b",
-                "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"
-            ],
-            "index": "pypi",
-            "version": "==2023.3.post1"
-        },
-        "requests": {
-            "hashes": [
-                "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
-                "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
-            ],
-            "index": "pypi",
-            "version": "==2.31.0"
-        },
-        "urllib3": {
-            "hashes": [
-                "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84",
-                "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"
-            ],
-            "markers": "python_version >= '3.7'",
-            "version": "==2.0.7"
-        },
-        "zipp": {
-            "hashes": [
-                "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31",
-                "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"
-            ],
-            "markers": "python_version >= '3.8'",
-            "version": "==3.17.0"
-        }
-    }
-}
diff --git a/README.rst b/README.rst
index fd7d405..b3176e9 100644
--- a/README.rst
+++ b/README.rst
@@ -206,8 +206,8 @@ Running Tests
 GitHub Actions are set up to automatically run unit tests against any new
 commits to the repo. To run these tests yourself: ::
 
-    pipenv install --dev
-    pipenv run pytest
+    pip install -e .[dev]
+    pytest .
 
 
 Changelog
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..e2963cf
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,35 @@
+[build-system]
+requires = ["setuptools"]
+
+[project]
+name = "us"
+description = "US state meta information and other fun stuff"
+readme = "README.rst"
+urls.Homepage = "https://github.com/unitedstates/python-us/"
+urls."Issue tracker" = "https://github.com/unitedstates/python-us/issues"
+requires-python = ">=3.7"
+dynamic = ["version"]
+authors = [{ name = "Jeremy Carbaugh", email = "jeremy@jcarbaugh.com" }]
+classifiers = [
+    "Programming Language :: Python",
+    "Programming Language :: Python :: 3.8",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12"
+]
+license = { file = "LICENSE"}
+
+dependencies = ['jellyfish']
+
+[project.optional-dependencies]
+dev = ['flake8', 'black', 'pytest', 'pytz']
+
+
+[tool.setuptools.dynamic]
+version = { attr = "us.version.__version__" }
+
+[tool.black]
+line-length = 120
+target-version = ['py39', 'py310', 'py311', 'py312']
+include = '\.pyi?$'
diff --git a/setup.py b/setup.py
deleted file mode 100644
index c7f1807..0000000
--- a/setup.py
+++ /dev/null
@@ -1,27 +0,0 @@
-from setuptools import setup, find_packages
-
-long_description = open("README.rst").read()
-
-setup(
-    name="us",
-    version="3.1.1",
-    author="Jeremy Carbaugh",
-    author_email="jeremy@jcarbaugh.com",
-    url="https://github.com/unitedstates/python-us",
-    description="US state meta information and other fun stuff",
-    long_description=long_description,
-    license="BSD",
-    packages=find_packages(),
-    include_package_data=True,
-    install_requires=["jellyfish==1.*"],
-    entry_points={"console_scripts": ["states = us.cli.states:main"]},
-    platforms=["any"],
-    classifiers=[
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: 3.9",
-        "Programming Language :: Python :: 3.10",
-        "Programming Language :: Python :: 3.11",
-    ],
-)
diff --git a/us/__init__.py b/us/__init__.py
index 0277e10..7c20534 100644
--- a/us/__init__.py
+++ b/us/__init__.py
@@ -7,3 +7,4 @@
     OBSOLETE,  # noqa
 )  # noqa
 from .unitedstatesofamerica import *  # noqa
+from .version import __version__ as version
diff --git a/us/cli/states.py b/us/cli/states.py
index e8107c3..a6f1f3f 100644
--- a/us/cli/states.py
+++ b/us/cli/states.py
@@ -6,9 +6,7 @@ def main():
     import argparse
 
     parser = argparse.ArgumentParser(description="Lookup state information")
-    parser.add_argument(
-        "query", metavar="QUERY", nargs=1, help="name, abbreviation, or FIPS code"
-    )
+    parser.add_argument("query", metavar="QUERY", nargs=1, help="name, abbreviation, or FIPS code")
 
     args = parser.parse_args()
 
@@ -23,10 +21,7 @@ def main():
         region = "territory" if data.pop("is_territory") else "state"
 
         sys.stdout.write("\n")
-        sys.stdout.write(
-            "*** The great %s of %s (%s) ***\n\n"
-            % (region, data.pop("name"), data.pop("abbr"))
-        )
+        sys.stdout.write("*** The great %s of %s (%s) ***\n\n" % (region, data.pop("name"), data.pop("abbr")))
 
         sys.stdout.write("  FIPS code: %s\n" % data.pop("fips"))
 
diff --git a/us/states.py b/us/states.py
index 1e67ffc..c9f7fee 100644
--- a/us/states.py
+++ b/us/states.py
@@ -108,9 +108,7 @@ def lookup(val, field: Optional[str] = None, use_cache: bool = True) -> Optional
     return matched_state
 
 
-def mapping(
-    from_field: str, to_field: str, states: Optional[Iterable[State]] = None
-) -> Dict[Any, Any]:
+def mapping(from_field: str, to_field: str, states: Optional[Iterable[State]] = None) -> Dict[Any, Any]:
     if states is None:
         states = STATES_AND_TERRITORIES
     return {getattr(s, from_field): getattr(s, to_field) for s in states}
diff --git a/us/version.py b/us/version.py
new file mode 100644
index 0000000..911557b
--- /dev/null
+++ b/us/version.py
@@ -0,0 +1,1 @@
+__version__ = "3.1.2"


Test patch:
diff --git a/us/tests/test_us.py b/us/tests/test_us.py
index 02a8c76..5f3572e 100644
--- a/us/tests/test_us.py
+++ b/us/tests/test_us.py
@@ -86,9 +86,7 @@ def test_jellyfish_metaphone():
 
 def test_mapping():
     states = us.STATES[:5]
-    assert us.states.mapping("abbr", "fips", states=states) == dict(
-        (s.abbr, s.fips) for s in states
-    )
+    assert us.states.mapping("abbr", "fips", states=states) == dict((s.abbr, s.fips) for s in states)
 
 
 def test_obsolete_mapping():