Repository: CartoDB/cartoframes
Pull request #1757 (2023-03-09 08:47:41)

Release/1.2.5
PR to update master with the next (micro) release 1.2.2. 

- [x] Run notebook tests
![Screenshot from 2023-03-09 10-43-23](https://user-images.githubusercontent.com/6042873/224000919-0e5fcb91-4754-4b61-b7d0-fc251ba26a7a.png)
- [x] Update CHANGELOG.md
- [x] Update README.rst
- [x] Update _version.py
- [x] Tested documentation in staging (see PR [here](https://github.com/CartoDB/developers/pull/774))

Next steps (after merging):
- [ ] Create and push tag `v1.2.5` (CF)
- [ ] Copy changelog information in tag's description (CF)
- [ ] Publish the package in PyPi
- [ ] Merge master into develop (CF)
- [ ] Create docs branch in CF (CF)
- [ ] Deploy dev center into production  (Dev)


Patch:
diff --git a/.github/workflows/cartoframes-ci.yml b/.github/workflows/cartoframes-ci.yml
index eb8b8ef7..f9f17221 100644
--- a/.github/workflows/cartoframes-ci.yml
+++ b/.github/workflows/cartoframes-ci.yml
@@ -10,18 +10,27 @@ on:
 jobs:
   test:
     strategy:
+      fail-fast: false
       matrix:
-        python-version: [3.5, 3.6, 3.7, 3.8]
+        include:
+          - os: ubuntu-20.04
+            python-version: 3.5
+          - os: ubuntu-20.04
+            python-version: 3.6
+          - os: ubuntu-latest
+            python-version: 3.7
+          - os: ubuntu-latest
+            python-version: 3.8
 
     name: Run tests on Python ${{ matrix.python-version }}
 
-    runs-on: ubuntu-latest
+    runs-on: ${{ matrix.os }}
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v3
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v1
+      uses: actions/setup-python@v4
       with:
         python-version: ${{ matrix.python-version }}
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e2e9ba7..c6418c2e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
+
+## [1.2.5] - 2022-03-09
+
+### Changed
+
+- Add compatibility with shapely>=1.7 (#1755)
+
 ## [1.2.4] - 2021-09-02
 
 ### Changed
diff --git a/Makefile b/Makefile
index 5456d0a2..b6d32b9b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,9 @@
 init:
-	pip install -e .
+	pip install -U pip
+	pip install -e .[tests]
+
+lint:
+	flake8 cartoframes tests
 
 test:
 	pytest tests/unit/
diff --git a/README.rst b/README.rst
index 4db4a6c1..f01e7b7d 100644
--- a/README.rst
+++ b/README.rst
@@ -4,8 +4,8 @@ CARTOframes
 
 .. image:: https://github.com/CartoDB/cartoframes/actions/workflows/cartoframes-ci.yml/badge.svg?branch=develop
     :target: https://github.com/CartoDB/cartoframes/actions/workflows/cartoframes-ci.yml
-.. image:: https://img.shields.io/badge/pypi-v1.2.4-orange
-    :target: https://pypi.org/project/cartoframes/1.2.4
+.. image:: https://img.shields.io/badge/pypi-v1.2.5-orange
+    :target: https://pypi.org/project/cartoframes/1.2.5
 
 A Python package for integrating `CARTO <https://carto.com/>`__ maps, analysis, and data services into data science workflows.
 
@@ -14,11 +14,11 @@ Python data analysis workflows often rely on the de facto standards `pandas <htt
 Try it Out
 ==========
 
-* Stable (1.2.4): |stable|
+* Stable (1.2.5): |stable|
 * Latest (develop branch): |develop|
 
 .. |stable| image:: https://mybinder.org/badge_logo.svg
-    :target: https://mybinder.org/v2/gh/cartodb/cartoframes/v1.2.4?filepath=examples
+    :target: https://mybinder.org/v2/gh/cartodb/cartoframes/v1.2.5?filepath=examples
 
 .. |develop| image:: https://mybinder.org/badge_logo.svg
     :target: https://mybinder.org/v2/gh/cartodb/cartoframes/develop?filepath=examples
diff --git a/cartoframes/_version.py b/cartoframes/_version.py
index daab838b..09964d63 100644
--- a/cartoframes/_version.py
+++ b/cartoframes/_version.py
@@ -1,1 +1,1 @@
-__version__ = '1.2.4'
+__version__ = '1.2.5'
diff --git a/cartoframes/data/observatory/catalog/dataset.py b/cartoframes/data/observatory/catalog/dataset.py
index 908786be..ced41526 100644
--- a/cartoframes/data/observatory/catalog/dataset.py
+++ b/cartoframes/data/observatory/catalog/dataset.py
@@ -500,8 +500,9 @@ class Dataset(CatalogEntity):
 
     @check_do_enabled
     def subscription_info(self, credentials=None):
-        """Get the subscription information of a Dataset, which includes the license, Terms of Service, rights, price, and
-        estimated time of delivery, among other metadata of interest during the :py:attr:`Dataset.subscription` process.
+        """Get the subscription information of a Dataset, which includes the license, Terms of Service, rights, price,
+        and estimated time of delivery, among other metadata of interest during the :py:attr:`Dataset.subscription`
+        process.
 
         Args:
             credentials (:py:class:`Credentials <cartoframes.auth.Credentials>`, optional):
diff --git a/cartoframes/io/managers/context_manager.py b/cartoframes/io/managers/context_manager.py
index 360998bd..ef2b11dc 100644
--- a/cartoframes/io/managers/context_manager.py
+++ b/cartoframes/io/managers/context_manager.py
@@ -168,7 +168,7 @@ class ContextManager:
     def delete_table(self, table_name):
         query = _drop_table_query(table_name)
         output = self.execute_query(query)
-        return not('notices' in output and 'does not exist' in output['notices'][0])
+        return not ('notices' in output and 'does not exist' in output['notices'][0])
 
     def _delete_function(self, function_name):
         query = _drop_function_query(function_name)
diff --git a/cartoframes/utils/geom_utils.py b/cartoframes/utils/geom_utils.py
index dbd8d4ce..b784fed8 100644
--- a/cartoframes/utils/geom_utils.py
+++ b/cartoframes/utils/geom_utils.py
@@ -218,7 +218,7 @@ def _load_ewkt(egeom):
     srid, geom = _extract_srid(egeom)
     ogeom = _load_wkt(geom)
     if srid:
-        shapely.geos.lgeos.GEOSSetSRID(ogeom._geom, int(srid))
+        ogeom = set_srid(ogeom, int(srid))
     return ogeom
 
 
@@ -241,7 +241,7 @@ def encode_geometry_ewkt(geom, srid=4326):
 
 def encode_geometry_ewkb(geom, srid=4326):
     if isinstance(geom, shapely.geometry.base.BaseGeometry):
-        shapely.geos.lgeos.GEOSSetSRID(geom._geom, srid)
+        geom = set_srid(geom, srid)
         return shapely.wkb.dumps(geom, hex=True, include_srid=True)
 
 
@@ -272,3 +272,18 @@ def get_crs(gdf):
         return gdf.crs['init']
     else:
         return str(gdf.crs)
+
+
+def get_srid(geom):
+    if shapely.__version__ < '2.0':
+        return shapely.geos.lgeos.GEOSGetSRID(geom._geom)
+    else:
+        return shapely.get_srid(geom)
+
+
+def set_srid(geom, srid):
+    if shapely.__version__ < '2.0':
+        shapely.geos.lgeos.GEOSSetSRID(geom._geom, int(srid))
+        return geom
+    else:
+        return shapely.set_srid(geom, int(srid))
diff --git a/setup.py b/setup.py
index 93b88715..705ab2b0 100644
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,7 @@ def get_version():
 REQUIRES = [
     'appdirs>=1.4.3,<2.0',
     'carto>=1.11.3,<2.0',
+    'markupsafe<=2.0.1',
     'jinja2>=2.10.1,<3.0',
     'pandas>=0.25.0',
     'geopandas>=0.6.0,<1.0',


Test patch:
diff --git a/tests/unit/io/test_carto.py b/tests/unit/io/test_carto.py
index b3638f21..3ff84c62 100644
--- a/tests/unit/io/test_carto.py
+++ b/tests/unit/io/test_carto.py
@@ -5,7 +5,6 @@ import random
 from pandas import Index
 from geopandas import GeoDataFrame
 from shapely.geometry import Point
-from shapely.geometry.base import BaseGeometry
 from shapely import wkt
 
 from carto.exceptions import CartoException
@@ -83,17 +82,18 @@ def test_read_carto_basegeometry_as_null_geom_value(mocker):
     })
 
     # When
-    gdf = read_carto('__source__', CREDENTIALS, null_geom_value=BaseGeometry())
+    gdf = read_carto('__source__', CREDENTIALS, null_geom_value=None)
 
     # Then
     expected = GeoDataFrame({
         'cartodb_id': [1],
         'the_geom': [
-            BaseGeometry()
+            None
         ]
     }, geometry='the_geom')
 
     cm_mock.assert_called_once_with('__source__', None, None, 3)
+    print(expected, gdf)
     assert expected.equals(gdf)
     assert gdf.crs == 'epsg:4326'
 
diff --git a/tests/unit/utils/test_geom_utils.py b/tests/unit/utils/test_geom_utils.py
index 9f0d3734..51d12730 100644
--- a/tests/unit/utils/test_geom_utils.py
+++ b/tests/unit/utils/test_geom_utils.py
@@ -3,12 +3,12 @@
 import pandas as pd
 import geopandas as gpd
 
-from shapely.geos import lgeos
 from shapely.geometry import Point
 
 from cartoframes.utils.geom_utils import (ENC_EWKT, ENC_SHAPELY, ENC_WKB,
                                           ENC_WKB_BHEX, ENC_WKB_HEX, ENC_WKT,
-                                          decode_geometry, decode_geometry_item, detect_encoding_type)
+                                          decode_geometry, decode_geometry_item,
+                                          detect_encoding_type, get_srid)
 
 
 class TestGeomUtils(object):
@@ -92,38 +92,38 @@ class TestGeomUtils(object):
     def test_decode_geometry_wkb(self):
         geom = decode_geometry_item(
             b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00H\x93@\x00\x00\x00\x00\x00\x9d\xb6@', ENC_WKB)
-        assert lgeos.GEOSGetSRID(geom._geom) == 0
+        assert get_srid(geom) == 0
         assert geom.wkb == b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00H\x93@\x00\x00\x00\x00\x00\x9d\xb6@'
 
         geom = decode_geometry_item(
             b'\x01\x01\x00\x00 \xe6\x10\x00\x00\x00\x00\x00\x00\x00H\x93@\x00\x00\x00\x00\x00\x9d\xb6@', ENC_WKB)  # ext
-        assert lgeos.GEOSGetSRID(geom._geom) == 4326
+        assert get_srid(geom) == 4326
         assert geom.wkb == b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00H\x93@\x00\x00\x00\x00\x00\x9d\xb6@'
 
     def test_decode_geometry_wkb_hex(self):
         geom = decode_geometry_item('0101000000000000000048934000000000009DB640', ENC_WKB_HEX)
-        assert lgeos.GEOSGetSRID(geom._geom) == 0
+        assert get_srid(geom) == 0
         assert geom.wkb_hex == '0101000000000000000048934000000000009DB640'
 
         geom = decode_geometry_item('0101000020E6100000000000000048934000000000009DB640', ENC_WKB_HEX)  # ext
-        assert lgeos.GEOSGetSRID(geom._geom) == 4326
+        assert get_srid(geom) == 4326
         assert geom.wkb_hex == '0101000000000000000048934000000000009DB640'
 
     def test_decode_geometry_wkb_bhex(self):
         geom = decode_geometry_item(b'0101000000000000000048934000000000009DB640', ENC_WKB_BHEX)
-        assert lgeos.GEOSGetSRID(geom._geom) == 0
+        assert get_srid(geom) == 0
         assert geom.wkb == b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00H\x93@\x00\x00\x00\x00\x00\x9d\xb6@'
 
         geom = decode_geometry_item(b'0101000020E6100000000000000048934000000000009DB640', ENC_WKB_BHEX)  # ext
-        assert lgeos.GEOSGetSRID(geom._geom) == 4326
+        assert get_srid(geom) == 4326
         assert geom.wkb == b'\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00H\x93@\x00\x00\x00\x00\x00\x9d\xb6@'
 
     def test_decode_geometry_wkt(self):
         geom = decode_geometry_item('POINT (1234 5789)', ENC_WKT)
-        assert lgeos.GEOSGetSRID(geom._geom) == 0
+        assert get_srid(geom) == 0
         assert geom.wkt == 'POINT (1234 5789)'
 
     def test_decode_geometry_ewkt(self):
         geom = decode_geometry_item('SRID=4326;POINT (1234 5789)', ENC_EWKT)  # ext
-        assert lgeos.GEOSGetSRID(geom._geom) == 4326
+        assert get_srid(geom) == 4326
         assert geom.wkt == 'POINT (1234 5789)'