From be98dde1595c1fe2a6d0e241ab9b7ddf367d8532 Mon Sep 17 00:00:00 2001 From: Wei Ji Date: Sat, 30 May 2020 15:53:28 +1200 Subject: [PATCH] :white_check_mark: Properly tested lonlat_to_xy function Collapse the geographic reprojection code into a one-liner! Basically wraps around pyproj, and handles lazy dask.DataFrame and xarray.DataArray objects by including the will-be released workaround for handling __array__ objects (scheduled for pyproj 3.0). Reinstated the 'catalog' variable in atl06_play.ipynb, as it's used further down the notebook. Also hashing python files in deepicedrain to check whether we should bust the CI cache to reinstall `deepicedrain`, instead of manually bumping dependencies each time. That said, we'll bump up pyzmq from 19.0.0 to 19.0.1 and keep doing random bumps until this branch is merged into master. --- .github/workflows/python-app.yml | 2 +- atl06_play.ipynb | 29 ++++------ atl06_play.py | 21 +++---- atl11_play.ipynb | 17 +----- atl11_play.py | 11 +--- deepicedrain/__init__.py | 2 +- deepicedrain/spatiotemporal.py | 26 +++++++++ .../tests/test_spatiotemporal_conversions.py | 39 ++++++++++++- poetry.lock | 58 +++++++++---------- 9 files changed, 118 insertions(+), 87 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 630692e..608beda 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -38,7 +38,7 @@ jobs: with: path: | /usr/share/miniconda3/envs/deepicedrain - key: cache-venv-${{ github.ref }}-${{ hashFiles('**/environment.yml') }}-${{ hashFiles('**/poetry.lock') }} + key: cache-venv-${{ github.ref }}-${{ hashFiles('**/environment.yml') }}-${{ hashFiles('**/poetry.lock') }}-${{ hashFiles('**/deepicedrain/*.py') }} restore-keys: | cache-venv-refs/heads/master- diff --git a/atl06_play.ipynb b/atl06_play.ipynb index 38b1836..0fa589a 100644 --- a/atl06_play.ipynb +++ b/atl06_play.ipynb @@ -109,7 +109,13 @@ "\n", "Use our [intake catalog](https://intake.readthedocs.io/en/latest/catalog.html) to get some sample ATL06 data\n", "(while making sure we have our Earthdata credentials set up properly),\n", - "and view it using [xarray](https://xarray.pydata.org) and [hvplot](https://hvplot.pyviz.org)." + "and view it using [xarray](https://xarray.pydata.org) and [hvplot](https://hvplot.pyviz.org).\n", + "\n", + "open the local intake data catalog file containing ICESat-2 stuff\n", + "catalog = intake.open_catalog(\"deepicedrain/atlas_catalog.yaml\")\n", + "or if the deepicedrain python package is installed, you can use either of the below:\n", + "catalog = deepicedrain.catalog\n", + "catalog = intake.cat.atlas_cat" ] }, { @@ -1041,10 +1047,8 @@ " )\n", " raise\n", "\n", - "# open the local intake data catalog file containing ICESat-2 stuff\n", "# data download will depend on having a .netrc file in home folder\n", - "# dataset = intake.cat.atlas_cat.icesat2atl06.to_dask().unify_chunks()\n", - "dataset = deepicedrain.catalog.icesat2atl06.to_dask().unify_chunks()\n", + "dataset = catalog.icesat2atl06.to_dask().unify_chunks()\n", "dataset" ] }, @@ -1744,21 +1748,8 @@ "metadata": {}, "outputs": [], "source": [ - "transformer = pyproj.Transformer.from_crs(\n", - " crs_from=pyproj.CRS.from_epsg(4326),\n", - " crs_to=pyproj.CRS.from_epsg(3031),\n", - " always_xy=True,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "metadata": {}, - "outputs": [], - "source": [ - "dfs[\"x\"], dfs[\"y\"] = transformer.transform(\n", - " xx=dfs.longitude.values, yy=dfs.latitude.values\n", + "dfs[\"x\"], dfs[\"y\"] = deepicedrain.lonlat_to_xy(\n", + " longitude=dfs.longitude, latitude=dfs.latitude\n", ")" ] }, diff --git a/atl06_play.py b/atl06_play.py index 2a8ec67..4a95868 100644 --- a/atl06_play.py +++ b/atl06_play.py @@ -73,6 +73,12 @@ # (while making sure we have our Earthdata credentials set up properly), # and view it using [xarray](https://xarray.pydata.org) and [hvplot](https://hvplot.pyviz.org). +# open the local intake data catalog file containing ICESat-2 stuff +catalog = intake.open_catalog("deepicedrain/atlas_catalog.yaml") +# or if the deepicedrain python package is installed, you can use either of the below: +# catalog = deepicedrain.catalog +# catalog = intake.cat.atlas_cat + # %% try: netrc.netrc() @@ -84,10 +90,8 @@ ) raise -# open the local intake data catalog file containing ICESat-2 stuff # data download will depend on having a .netrc file in home folder -# dataset = intake.cat.atlas_cat.icesat2atl06.to_dask().unify_chunks() -dataset = deepicedrain.catalog.icesat2atl06.to_dask().unify_chunks() +dataset = catalog.icesat2atl06.to_dask().unify_chunks() dataset # %% @@ -343,15 +347,8 @@ def six_laser_beams(filepaths: list) -> dask.dataframe.DataFrame: # ### Transform from EPSG:4326 (lat/lon) to EPSG:3031 (Antarctic Polar Stereographic) # %% -transformer = pyproj.Transformer.from_crs( - crs_from=pyproj.CRS.from_epsg(4326), - crs_to=pyproj.CRS.from_epsg(3031), - always_xy=True, -) - -# %% -dfs["x"], dfs["y"] = transformer.transform( - xx=dfs.longitude.values, yy=dfs.latitude.values +dfs["x"], dfs["y"] = deepicedrain.lonlat_to_xy( + longitude=dfs.longitude, latitude=dfs.latitude ) # %% diff --git a/atl11_play.ipynb b/atl11_play.ipynb index 8769a21..c5b02c5 100644 --- a/atl11_play.ipynb +++ b/atl11_play.ipynb @@ -159,24 +159,11 @@ }, "outputs": [], "source": [ - "lonlat_to_xy = lambda longitude, latitude: pyproj.Proj(projparams=3031)(\n", - " longitude, latitude\n", + "ds[\"x\"], ds[\"y\"] = deepicedrain.lonlat_to_xy(\n", + " longitude=ds.longitude, latitude=ds.latitude\n", ")" ] }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "lines_to_next_cell": 2 - }, - "outputs": [], - "source": [ - "x, y = lonlat_to_xy(ds.longitude.values, ds.latitude.values)\n", - "ds[\"x\"] = xr.DataArray(data=x, coords=ds.longitude.coords)\n", - "ds[\"y\"] = xr.DataArray(data=y, coords=ds.latitude.coords)" - ] - }, { "cell_type": "code", "execution_count": 7, diff --git a/atl11_play.py b/atl11_play.py index ebe7035..fcb74c8 100644 --- a/atl11_play.py +++ b/atl11_play.py @@ -86,17 +86,10 @@ # to the Antarctic Polar Stereographic (EPSG:3031) projection. # %% -lonlat_to_xy = lambda longitude, latitude: pyproj.Proj(projparams=3031)( - longitude, latitude +ds["x"], ds["y"] = deepicedrain.lonlat_to_xy( + longitude=ds.longitude, latitude=ds.latitude ) - -# %% -x, y = lonlat_to_xy(ds.longitude.values, ds.latitude.values) -ds["x"] = xr.DataArray(data=x, coords=ds.longitude.coords) -ds["y"] = xr.DataArray(data=y, coords=ds.latitude.coords) - - # %% # Also set x, y as coordinates in xarray.Dataset ds = ds.set_coords(names=["x", "y"]) diff --git a/deepicedrain/__init__.py b/deepicedrain/__init__.py index c23e436..f695f62 100644 --- a/deepicedrain/__init__.py +++ b/deepicedrain/__init__.py @@ -5,7 +5,7 @@ import deepicedrain from deepicedrain.deltamath import calculate_delta -from deepicedrain.spatiotemporal import Region, deltatime_to_utctime +from deepicedrain.spatiotemporal import Region, deltatime_to_utctime, lonlat_to_xy __version__: str = "0.1.0" diff --git a/deepicedrain/spatiotemporal.py b/deepicedrain/spatiotemporal.py index 3bafaf2..3d95be4 100644 --- a/deepicedrain/spatiotemporal.py +++ b/deepicedrain/spatiotemporal.py @@ -6,6 +6,7 @@ import datetime import numpy as np +import pyproj import xarray as xr @@ -72,3 +73,28 @@ def deltatime_to_utctime( utc_time: xr.DataArray = dataarray.__class__(start_epoch) + dataarray return utc_time + + +def lonlat_to_xy( + longitude: xr.DataArray, latitude: xr.DataArray, epsg: int = 3031 +) -> (xr.DataArray, xr.DataArray): + """ + Reprojects longitude/latitude EPSG:4326 coordinates to x/y coordinates. + Default conversion is to Antarctic Stereographic Projection EPSG:3031. + """ + if hasattr(longitude, "__array__") and callable(longitude.__array__): + # TODO upgrade to PyProj 3.0 to remove this workaround for passing in + # dask.dataframe.core.Series or xarray.DataArray objects + # Based on https://github.com/pyproj4/pyproj/pull/625 + _longitude = longitude.__array__() + _latitude = latitude.__array__() + + x, y = pyproj.Proj(projparams=epsg)(_longitude, _latitude) + + if hasattr(longitude, "coords"): + return ( + xr.DataArray(data=x, coords=longitude.coords), + xr.DataArray(data=y, coords=latitude.coords), + ) + else: + return x, y diff --git a/deepicedrain/tests/test_spatiotemporal_conversions.py b/deepicedrain/tests/test_spatiotemporal_conversions.py index b863a37..d6f03e4 100644 --- a/deepicedrain/tests/test_spatiotemporal_conversions.py +++ b/deepicedrain/tests/test_spatiotemporal_conversions.py @@ -9,7 +9,7 @@ import pandas as pd import xarray as xr -from deepicedrain import catalog, deltatime_to_utctime +from deepicedrain import catalog, deltatime_to_utctime, lonlat_to_xy def test_deltatime_to_utctime(): @@ -45,3 +45,40 @@ def test_deltatime_to_utctime(): ) atl11_dataset.close() + + +def test_lonlat_to_xy_dask_series(): + """ + Test that converting from longitude/latitude to x/y in EPSG:3031 works when + passing them in as dask.dataframe.core.Series objects. + """ + atl11_dataset: xr.Dataset = catalog.test_data.atl11_test_case.to_dask() + atl11_dataframe: dask.dataframe.core.DataFrame = atl11_dataset.to_dask_dataframe() + + x, y = lonlat_to_xy( + longitude=atl11_dataframe.longitude, latitude=atl11_dataframe.latitude, + ) + npt.assert_equal(actual=x.mean(), desired=-56900105.00307033) + npt.assert_equal(actual=y.mean(), desired=48141607.48486084) + + atl11_dataset.close() + + +def test_lonlat_to_xy_xarray_dataarray(): + """ + Test that converting from longitude/latitude to x/y in EPSG:3031 works when + passing them in as xarray.DataArray objects. Ensure that the xarray + dimensions are preserved in the process. + """ + atl11_dataset: xr.Dataset = catalog.test_data.atl11_test_case.to_dask() + + x, y = lonlat_to_xy( + longitude=atl11_dataset.longitude, latitude=atl11_dataset.latitude + ) + + assert x.dims == y.dims == ("ref_pt",) + assert x.shape == y.shape == (1404,) + npt.assert_equal(actual=x.mean().data, desired=-56900105.00307034) + npt.assert_equal(actual=y.mean().data, desired=48141607.48486084) + + atl11_dataset.close() diff --git a/poetry.lock b/poetry.lock index df849c0..23fd459 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1532,7 +1532,7 @@ description = "Python bindings for 0MQ" name = "pyzmq" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*" -version = "19.0.0" +version = "19.0.1" [[package]] category = "dev" @@ -2706,34 +2706,34 @@ pyyaml = [ {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, ] pyzmq = [ - {file = "pyzmq-19.0.0-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:3f12ce1e9cc9c31497bd82b207e8e86ccda9eebd8c9f95053aae46d15ccd2196"}, - {file = "pyzmq-19.0.0-cp27-cp27m-win32.whl", hash = "sha256:e8e4efb52ec2df8d046395ca4c84ae0056cf507b2f713ec803c65a8102d010de"}, - {file = "pyzmq-19.0.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f5b6d015587a1d6f582ba03b226a9ddb1dfb09878b3be04ef48b01b7d4eb6b2a"}, - {file = "pyzmq-19.0.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:bb10361293d96aa92be6261fa4d15476bca56203b3a11c62c61bd14df0ef89ba"}, - {file = "pyzmq-19.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4557d5e036e6d85715b4b9fdb482081398da1d43dc580d03db642b91605b409f"}, - {file = "pyzmq-19.0.0-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:84b91153102c4bcf5d0f57d1a66a0f03c31e9e6525a5f656f52fc615a675c748"}, - {file = "pyzmq-19.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6aaaf90b420dc40d9a0e1996b82c6a0ff91d9680bebe2135e67c9e6d197c0a53"}, - {file = "pyzmq-19.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:ad48865a29efa8a0cecf266432ea7bc34e319954e55cf104be0319c177e6c8f5"}, - {file = "pyzmq-19.0.0-cp35-cp35m-win32.whl", hash = "sha256:32234c21c5e0a767c754181c8112092b3ddd2e2a36c3f76fc231ced817aeee47"}, - {file = "pyzmq-19.0.0-cp35-cp35m-win_amd64.whl", hash = "sha256:f37c29da2a5b0c5e31e6f8aab885625ea76c807082f70b2d334d3fd573c3100a"}, - {file = "pyzmq-19.0.0-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:1e076ad5bd3638a18c376544d32e0af986ca10d43d4ce5a5d889a8649f0d0a3d"}, - {file = "pyzmq-19.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:f4d558bc5668d2345773a9ff8c39e2462dafcb1f6772a2e582fbced389ce527f"}, - {file = "pyzmq-19.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f562dab21c03c7aa061f63b147a595dbe1006bf4f03213272fc9f7d5baec791"}, - {file = "pyzmq-19.0.0-cp36-cp36m-win32.whl", hash = "sha256:7f7e7b24b1d392bb5947ba91c981e7d1a43293113642e0d8870706c8e70cdc71"}, - {file = "pyzmq-19.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:75238d3c16cab96947705d5709187a49ebb844f54354cdf0814d195dd4c045de"}, - {file = "pyzmq-19.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cb3b7156ef6b1a119e68fbe3a54e0a0c40ecacc6b7838d57dd708c90b62a06dc"}, - {file = "pyzmq-19.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:a99ae601b4f6917985e9bb071549e30b6f93c72f5060853e197bdc4b7d357e5f"}, - {file = "pyzmq-19.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:242d949eb6b10197cda1d1cec377deab1d5324983d77e0d0bf9dc5eb6d71a6b4"}, - {file = "pyzmq-19.0.0-cp37-cp37m-win32.whl", hash = "sha256:a49fd42a29c1cc1aa9f461c5f2f5e0303adba7c945138b35ee7f4ab675b9f754"}, - {file = "pyzmq-19.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5f10a31f288bf055be76c57710807a8f0efdb2b82be6c2a2b8f9a61f33a40cea"}, - {file = "pyzmq-19.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:26f4ae420977d2a8792d7c2d7bda43128b037b5eeb21c81951a94054ad8b8843"}, - {file = "pyzmq-19.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:944f6bb5c63140d76494467444fd92bebd8674236837480a3c75b01fe17df1ab"}, - {file = "pyzmq-19.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:b08e425cf93b4e018ab21dc8fdbc25d7d0502a23cc4fea2380010cf8cf11e462"}, - {file = "pyzmq-19.0.0-cp38-cp38-win32.whl", hash = "sha256:a1f957c20c9f51d43903881399b078cddcf710d34a2950e88bce4e494dcaa4d1"}, - {file = "pyzmq-19.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:bd1a769d65257a7a12e2613070ca8155ee348aa9183f2aadf1c8b8552a5510f5"}, - {file = "pyzmq-19.0.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:0bbc1728fe4314b4ca46249c33873a390559edac7c217ec7001b5e0c34a8fb7f"}, - {file = "pyzmq-19.0.0-pp36-pypy36_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5e071b834051e9ecb224915398f474bfad802c2fff883f118ff5363ca4ae3edf"}, - {file = "pyzmq-19.0.0.tar.gz", hash = "sha256:5e1f65e576ab07aed83f444e201d86deb01cd27dcf3f37c727bc8729246a60a8"}, + {file = "pyzmq-19.0.1-cp27-cp27m-macosx_10_9_intel.whl", hash = "sha256:58688a2dfa044fad608a8e70ba8d019d0b872ec2acd75b7b5e37da8905605891"}, + {file = "pyzmq-19.0.1-cp27-cp27m-win32.whl", hash = "sha256:87c78f6936e2654397ca2979c1d323ee4a889eef536cc77a938c6b5be33351a7"}, + {file = "pyzmq-19.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:97b6255ae77328d0e80593681826a0479cb7bac0ba8251b4dd882f5145a2293a"}, + {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:15b4cb21118f4589c4db8be4ac12b21c8b4d0d42b3ee435d47f686c32fe2e91f"}, + {file = "pyzmq-19.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:931339ac2000d12fe212e64f98ce291e81a7ec6c73b125f17cf08415b753c087"}, + {file = "pyzmq-19.0.1-cp35-cp35m-macosx_10_9_intel.whl", hash = "sha256:2a88b8fabd9cc35bd59194a7723f3122166811ece8b74018147a4ed8489e6421"}, + {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:bafd651b557dd81d89bd5f9c678872f3e7b7255c1c751b78d520df2caac80230"}, + {file = "pyzmq-19.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:8952f6ba6ae598e792703f3134af5a01af8f5c7cf07e9a148f05a12b02412cea"}, + {file = "pyzmq-19.0.1-cp35-cp35m-win32.whl", hash = "sha256:54aa24fd60c4262286fc64ca632f9e747c7cc3a3a1144827490e1dc9b8a3a960"}, + {file = "pyzmq-19.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:dcbc3f30c11c60d709c30a213dc56e88ac016fe76ac6768e64717bd976072566"}, + {file = "pyzmq-19.0.1-cp36-cp36m-macosx_10_9_intel.whl", hash = "sha256:6ca519309703e95d55965735a667809bbb65f52beda2fdb6312385d3e7a6d234"}, + {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4ee0bfd82077a3ff11c985369529b12853a4064320523f8e5079b630f9551448"}, + {file = "pyzmq-19.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ba6f24431b569aec674ede49cad197cad59571c12deed6ad8e3c596da8288217"}, + {file = "pyzmq-19.0.1-cp36-cp36m-win32.whl", hash = "sha256:956775444d01331c7eb412c5fb9bb62130dfaac77e09f32764ea1865234e2ca9"}, + {file = "pyzmq-19.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b08780e3a55215873b3b8e6e7ca8987f14c902a24b6ac081b344fd430d6ca7cd"}, + {file = "pyzmq-19.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:21f7d91f3536f480cb2c10d0756bfa717927090b7fb863e6323f766e5461ee1c"}, + {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bfff5ffff051f5aa47ba3b379d87bd051c3196b0c8a603e8b7ed68a6b4f217ec"}, + {file = "pyzmq-19.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:07fb8fe6826a229dada876956590135871de60dbc7de5a18c3bcce2ed1f03c98"}, + {file = "pyzmq-19.0.1-cp37-cp37m-win32.whl", hash = "sha256:342fb8a1dddc569bc361387782e8088071593e7eaf3e3ecf7d6bd4976edff112"}, + {file = "pyzmq-19.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:faee2604f279d31312bc455f3d024f160b6168b9c1dde22bf62d8c88a4deca8e"}, + {file = "pyzmq-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5b9d21fc56c8aacd2e6d14738021a9d64f3f69b30578a99325a728e38a349f85"}, + {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:af0c02cf49f4f9eedf38edb4f3b6bb621d83026e7e5d76eb5526cc5333782fd6"}, + {file = "pyzmq-19.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5f1f2eb22aab606f808163eb1d537ac9a0ba4283fbeb7a62eb48d9103cf015c2"}, + {file = "pyzmq-19.0.1-cp38-cp38-win32.whl", hash = "sha256:f9d7e742fb0196992477415bb34366c12e9bb9a0699b8b3f221ff93b213d7bec"}, + {file = "pyzmq-19.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:5b99c2ae8089ef50223c28bac57510c163bfdff158c9e90764f812b94e69a0e6"}, + {file = "pyzmq-19.0.1-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:cf5d689ba9513b9753959164cf500079383bc18859f58bf8ce06d8d4bef2b054"}, + {file = "pyzmq-19.0.1-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:aaa8b40b676576fd7806839a5de8e6d5d1b74981e6376d862af6c117af2a3c10"}, + {file = "pyzmq-19.0.1.tar.gz", hash = "sha256:13a5638ab24d628a6ade8f794195e1a1acd573496c3b85af2f1183603b7bf5e0"}, ] regex = [ {file = "regex-2020.5.7-cp27-cp27m-win32.whl", hash = "sha256:5493a02c1882d2acaaf17be81a3b65408ff541c922bfd002535c5f148aa29f74"},