[Git][debian-gis-team/python-geojson][upstream] New upstream version 2.5.0

Bas Couwenberg gitlab at salsa.debian.org
Fri Jul 19 06:11:38 BST 2019



Bas Couwenberg pushed to branch upstream at Debian GIS Project / python-geojson


Commits:
066d8e09 by Bas Couwenberg at 2019-07-19T04:57:57Z
New upstream version 2.5.0
- - - - -


17 changed files:

- .travis.yml
- CHANGELOG.rst
- README.rst
- geojson/_version.py
- − geojson/crs.py
- geojson/factory.py
- geojson/geometry.py
- geojson/mapping.py
- geojson/utils.py
- setup.py
- tests/test_base.py
- tests/test_constructor.py
- tests/test_coords.py
- − tests/test_crs.py
- tests/test_features.py
- tests/test_geo_interface.py
- tox.ini


Changes:

=====================================
.travis.yml
=====================================
@@ -1,18 +1,42 @@
-language: "python"
+language: python
+dist: xenial
 python:
-   - "2.7"
-   - "3.3"
-   - "3.4"
-   - "3.5"
-   - "3.6"
-   - "pypy"
+- 2.7
+- 3.5
+- 3.6
+- 3.7
+- pypy2.7-6.0
+- pypy3.5-6.0
 install:
-   - pip install flake8
-   - pip install codecov
-before_script:
-   flake8 .
+- pip install codecov
 script:
-   - coverage run --branch --source=geojson setup.py test
-   - coverage xml
+- coverage run --branch --source=geojson setup.py test
+- coverage xml
 after_success:
-   - codecov
+- codecov
+stages:
+- lint
+- test
+- deploy
+jobs:
+  include:
+  - stage: lint
+    env: TOXENV=flake8
+    python: 3.7
+    install: pip install flake8
+    script: flake8 .
+  - stage: deploy
+    env:
+    python: 3.7
+    install: skip
+    script: skip
+    deploy:
+      provider: pypi
+      server: https://jazzband.co/projects/python-geojson/upload
+      distributions: sdist bdist_wheel
+      user: jazzband
+      password:
+         secure: V2dsQfSMdChZ+G9w4cBcoa/Gmo/kJf2BMaN3sb7pDcAkTHuPYHppfSfj3vWDfpZFNobg8kTed4xjeYdKJ/oxFMpSGBWhGGpzQauzXO8n6LFXM44r/b5uXzyOyzdwwnjWXBn3NkubNU/n3dSjnRr9ZTeXGjiOGTPW1wAWjARJvgg=
+      on:
+        tags: true
+        repo: jazzband/python-geojson


=====================================
CHANGELOG.rst
=====================================
@@ -1,6 +1,34 @@
 Changes
 =======
 
+2.5.0 (2019-07-18)
+------------------
+
+- Add "precision" parameter to GeoJSON Object classes with default precision of 6 (0.1m)
+
+  - https://github.com/jazzband/python-geojson/pull/131
+
+- Fix bug where `map_geometries()` util was not preserving Feature IDs
+
+  - https://github.com/jazzband/python-geojson/pull/128
+  - https://github.com/jazzband/python-geojson/pull/130
+
+- Remove `crs` module and features to conform to official WGS84-only GeoJSON spec
+
+  - https://github.com/jazzband/python-geojson/pull/124
+
+- Set up semi-automatic PyPi releases via Travis/Jazzband
+
+  - https://github.com/jazzband/python-geojson/pull/123
+
+2.4.2 (2019-03-12)
+------------------
+
+- Tie Travis CI to jazzband instance
+- Remove EOL 3.3 and 3.4 version support
+
+  - https://github.com/jazzband/python-geojson/pull/120
+
 2.4.1 (2018-10-17)
 ------------------
 


=====================================
README.rst
=====================================
@@ -1,10 +1,10 @@
 python-geojson
 ==============
 
-.. image:: https://img.shields.io/travis/frewsxcv/python-geojson.svg
-   :target: https://travis-ci.org/frewsxcv/python-geojson
-.. image:: https://img.shields.io/codecov/c/github/frewsxcv/python-geojson.svg
-   :target: https://codecov.io/github/frewsxcv/python-geojson?branch=master
+.. image:: https://img.shields.io/travis/jazzband/python-geojson.svg
+   :target: https://travis-ci.org/jazzband/python-geojson
+.. image:: https://img.shields.io/codecov/c/github/jazzband/python-geojson.svg
+   :target: https://codecov.io/github/jazzband/python-geojson?branch=master
 
 This library contains:
 
@@ -21,7 +21,7 @@ This library contains:
 Installation
 ------------
 
-python-geojson is compatible with Python 2.7, 3.3, 3.4, 3.5 and 3.6. It is listed on `PyPi as 'geojson'`_. The recommended way to install is via pip_:
+python-geojson is compatible with Python 2.7, 3.5, 3.6 and 3.7. It is listed on `PyPi as 'geojson'`_. The recommended way to install is via pip_:
 
 .. code::
 
@@ -266,6 +266,22 @@ This encoding/decoding functionality shown in the previous can be extended to cu
   >>> geojson.dumps(point_instance, sort_keys=True)  # doctest: +ELLIPSIS
   '{"coordinates": [52.23..., -19.23...], "type": "Point"}'
 
+Default and custom precision
+~~~~~~~~~~~~~~~~~
+
+GeoJSON Object-based classes in this package have an additional `precision` attribute which rounds off
+coordinates to 6 decimal places (roughly 0.1 meters) by default and can be customized per object instance.
+
+.. code:: python
+
+  >>> from geojson import Point
+
+  >>> Point((-115.123412341234, 37.123412341234))  # rounded to 6 decimal places by default
+  {"coordinates": [-115.123412, 37.123412], "type": "Point"}
+
+  >>> Point((-115.12341234, 37.12341234), precision=8)  # rounded to 8 decimal places
+  {"coordinates": [-115.12341234, 37.12341234], "type": "Point"}
+
 Helpful utilities
 -----------------
 
@@ -369,7 +385,9 @@ generate_random
 Development
 -----------
 
-To build this project, run :code:`python setup.py build`. To run the unit tests, run :code:`python setup.py test`.
+To build this project, run :code:`python setup.py build`.
+To run the unit tests, run :code:`python setup.py test`.
+To run the style checks, run :code:`flake8` (install `flake8` if needed).
 
 Credits
 -------
@@ -380,6 +398,7 @@ Credits
 * Blake Grotewold <hello at grotewold.me>
 * Zsolt Ero <zsolt.ero at gmail.com>
 * Sergey Romanov <xxsmotur at gmail.com>
+* Ray Riga <ray at strongoutput.com>
 
 
 .. _GeoJSON: http://geojson.org/


=====================================
geojson/_version.py
=====================================
@@ -1,2 +1,2 @@
-__version__ = "2.4.1"
+__version__ = "2.5.0"
 __version_info__ = tuple(map(int, __version__.split(".")))


=====================================
geojson/crs.py deleted
=====================================
@@ -1,39 +0,0 @@
-from geojson.base import GeoJSON
-
-
-class CoordinateReferenceSystem(GeoJSON):
-    """
-    Represents a CRS.
-    """
-
-    def __init__(self, properties=None, **extra):
-        super(CoordinateReferenceSystem, self).__init__(**extra)
-        self["properties"] = properties or {}
-
-
-class Named(CoordinateReferenceSystem):
-    """
-    Represents a named CRS.
-    """
-
-    def __init__(self, properties=None, **extra):
-        super(Named, self).__init__(properties=properties, **extra)
-        self["type"] = "name"
-
-    def __repr__(self):
-        return super(Named, self).__repr__()
-
-
-class Linked(CoordinateReferenceSystem):
-    """
-    Represents a linked CRS.
-    """
-
-    def __init__(self, properties=None, **extra):
-        super(Linked, self).__init__(properties=properties, **extra)
-        self["type"] = "link"
-
-
-class Default(object):
-
-    """GeoJSON default, long/lat WGS84, is not serialized."""


=====================================
geojson/factory.py
=====================================
@@ -3,13 +3,9 @@ from geojson.geometry import MultiLineString, MultiPoint, MultiPolygon
 from geojson.geometry import GeometryCollection
 from geojson.feature import Feature, FeatureCollection
 from geojson.base import GeoJSON
-from geojson.crs import Named, Linked
 
 __all__ = ([Point, LineString, Polygon] +
            [MultiLineString, MultiPoint, MultiPolygon] +
            [GeometryCollection] +
            [Feature, FeatureCollection] +
            [GeoJSON])
-
-name = Named
-link = Linked


=====================================
geojson/geometry.py
=====================================
@@ -16,28 +16,28 @@ class Geometry(GeoJSON):
     Represents an abstract base class for a WGS84 geometry.
     """
 
-    def __init__(self, coordinates=None, crs=None, validate=False, **extra):
+    def __init__(self, coordinates=None, validate=False, precision=6, **extra):
         """
         Initialises a Geometry object.
 
         :param coordinates: Coordinates of the Geometry object.
         :type coordinates: tuple or list of tuple
-        :param crs: CRS
-        :type crs: CRS object
+        :param validate: Raise exception if validation errors are present?
+        :type validate: boolean
+        :param precision: Number of decimal places for lat/lon coords.
+        :type precision: integer
         """
-
         super(Geometry, self).__init__(**extra)
-        self["coordinates"] = self.clean_coordinates(coordinates or [])
+        self["coordinates"] = self.clean_coordinates(
+            coordinates or [], precision)
 
         if validate:
             errors = self.errors()
             if errors:
                 raise ValueError('{}: {}'.format(errors, coordinates))
-        if crs:
-            self["crs"] = self.to_instance(crs, strict=True)
 
     @classmethod
-    def clean_coordinates(cls, coords):
+    def clean_coordinates(cls, coords, precision):
         if isinstance(coords, cls):
             return coords['coordinates']
 
@@ -46,11 +46,11 @@ class Geometry(GeoJSON):
             coords = [coords]
         for coord in coords:
             if isinstance(coord, (list, tuple)):
-                new_coords.append(cls.clean_coordinates(coord))
+                new_coords.append(cls.clean_coordinates(coord, precision))
             elif isinstance(coord, Geometry):
                 new_coords.append(coord['coordinates'])
             elif isinstance(coord, _JSON_compliant_types):
-                new_coords.append(coord)
+                new_coords.append(round(coord, precision))
             else:
                 raise ValueError("%r is not a JSON compliant number" % coord)
         return new_coords


=====================================
geojson/mapping.py
=====================================
@@ -1,4 +1,8 @@
-from collections import MutableMapping
+try:
+    from collections.abc import MutableMapping
+except ImportError:
+    from collections import MutableMapping
+
 try:
     import simplejson as json
 except ImportError:
@@ -7,9 +11,6 @@ except ImportError:
 import geojson
 
 
-mapping_base = MutableMapping
-
-
 GEO_INTERFACE_MARKER = "__geo_interface__"
 
 


=====================================
geojson/utils.py
=====================================
@@ -119,12 +119,8 @@ def map_geometries(func, obj):
         geoms = [func(geom) if geom else None for geom in obj['geometries']]
         return {'type': obj['type'], 'geometries': geoms}
     elif obj['type'] == 'Feature':
-        geom = func(obj['geometry']) if obj['geometry'] else None
-        return {
-            'type': obj['type'],
-            'geometry': geom,
-            'properties': obj['properties'],
-        }
+        obj['geometry'] = func(obj['geometry']) if obj['geometry'] else None
+        return obj
     elif obj['type'] == 'FeatureCollection':
         feats = [map_geometries(func, feat) for feat in obj['features']]
         return {'type': obj['type'], 'features': feats}


=====================================
setup.py
=====================================
@@ -29,9 +29,10 @@ def test_suite():
     return suite
 
 
-if sys.version_info[:2] not in [(2, 6), (2, 7)] and \
-        sys.version_info[:1] not in [(3, )]:
-    sys.stderr.write("Sorry, only Python 2.7, and 3.x are supported "
+major_version, minor_version = sys.version_info[:2]
+if not ((major_version == 2 and minor_version == 7)
+        or (major_version == 3 and minor_version >= 5)):
+    sys.stderr.write("Sorry, only Python 2.7, 3.5, 3.6 and 3.7 are supported "
                      "at this time.\n")
     exit(1)
 
@@ -47,9 +48,9 @@ setup(
     keywords="gis geography json",
     author="Sean Gillies",
     author_email="sgillies at frii.com",
-    maintainer="Corey Farwell",
-    maintainer_email="coreyf at rwell.org",
-    url="https://github.com/frewsxcv/python-geojson",
+    maintainer="Ray Riga",
+    maintainer_email="ray at strongoutput.com",
+    url="https://github.com/jazzband/python-geojson",
     long_description=readme_text,
     packages=["geojson"],
     package_dir={"geojson": "geojson"},
@@ -66,10 +67,9 @@ setup(
         "Programming Language :: Python :: 2",
         "Programming Language :: Python :: 2.7",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.3",
-        "Programming Language :: Python :: 3.4",
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: 3.7",
         "Topic :: Scientific/Engineering :: GIS",
     ]
 )


=====================================
tests/test_base.py
=====================================
@@ -61,7 +61,7 @@ class BaseTestCase(unittest.TestCase):
 
     def test_to_instance(self):
         FAKE = 'fake'
-        self.assertEquals(FAKE, geojson.GeoJSON.to_instance(
+        self.assertEqual(FAKE, geojson.GeoJSON.to_instance(
             None, (lambda: FAKE)))
 
         with self.assertRaises(ValueError):


=====================================
tests/test_constructor.py
=====================================
@@ -13,21 +13,21 @@ class TestGeoJSONConstructor(unittest.TestCase):
     def test_copy_construction(self):
         coords = [1, 2]
         pt = geojson.Point(coords)
-        self.assertEquals(geojson.Point(pt), pt)
+        self.assertEqual(geojson.Point(pt), pt)
 
     def test_nested_constructors(self):
         a = [5, 6]
         b = [9, 10]
         c = [-5, 12]
         mp = geojson.MultiPoint([geojson.Point(a), b])
-        self.assertEquals(mp.coordinates, [a, b])
+        self.assertEqual(mp.coordinates, [a, b])
 
         mls = geojson.MultiLineString([geojson.LineString([a, b]), [a, c]])
-        self.assertEquals(mls.coordinates, [[a, b], [a, c]])
+        self.assertEqual(mls.coordinates, [[a, b], [a, c]])
 
         outer = [a, b, c, a]
         poly = geojson.Polygon(geojson.MultiPoint(outer))
         other = [[1, 1], [1, 2], [2, 1], [1, 1]]
         poly2 = geojson.Polygon([outer, other])
-        self.assertEquals(geojson.MultiPolygon([poly, poly2]).coordinates,
-                          [[outer], [outer, other]])
+        self.assertEqual(geojson.MultiPolygon([poly, poly2]).coordinates,
+                         [[outer], [outer, other]])


=====================================
tests/test_coords.py
=====================================
@@ -3,12 +3,18 @@ import unittest
 import geojson
 from geojson.utils import coords, map_coords
 
+TOO_PRECISE = (1.12341234, -2.12341234)
+
 
 class CoordsTestCase(unittest.TestCase):
     def test_point(self):
         itr = coords(geojson.Point((-115.81, 37.24)))
         self.assertEqual(next(itr), (-115.81, 37.24))
 
+    def test_point_rounding(self):
+        itr = coords(geojson.Point(TOO_PRECISE))
+        self.assertEqual(next(itr), tuple([round(c, 6) for c in TOO_PRECISE]))
+
     def test_dict(self):
         itr = coords({'type': 'Point', 'coordinates': [-115.81, 37.24]})
         self.assertEqual(next(itr), (-115.81, 37.24))
@@ -66,6 +72,16 @@ class CoordsTestCase(unittest.TestCase):
         self.assertEqual(result['coordinates'][0][0][0], (3.78, 9.28))
         self.assertEqual(result['coordinates'][-1][-1][-1], (23.18, -34.29))
 
+    def test_map_feature(self):
+        g = geojson.Feature(
+            id='123',
+            geometry=geojson.Point([-115.81, 37.24])
+        )
+        result = map_coords(lambda x: x, g)
+        self.assertEqual(result['type'], 'Feature')
+        self.assertEqual(result['id'], '123')
+        self.assertEqual(result['geometry']['coordinates'], (-115.81, 37.24))
+
     def test_map_invalid(self):
         with self.assertRaises(ValueError):
             map_coords(lambda x: x, {"type": ""})


=====================================
tests/test_crs.py deleted
=====================================
@@ -1,30 +0,0 @@
-import unittest
-
-import geojson
-
-
-class CRSTest(unittest.TestCase):
-
-    def setUp(self):
-        self.crs = geojson.crs.Named(
-            properties={
-                "name": "urn:ogc:def:crs:EPSG::3785",
-            }
-        )
-
-    def test_crs_repr(self):
-        actual = repr(self.crs)
-        expected = ('{"properties": {"name": "urn:ogc:def:crs:EPSG::3785"},'
-                    ' "type": "name"}')
-        self.assertEqual(actual, expected)
-
-    def test_crs_encode(self):
-        actual = geojson.dumps(self.crs, sort_keys=True)
-        expected = ('{"properties": {"name": "urn:ogc:def:crs:EPSG::3785"},'
-                    ' "type": "name"}')
-        self.assertEqual(actual, expected)
-
-    def test_crs_decode(self):
-        dumped = geojson.dumps(self.crs)
-        actual = geojson.loads(dumped)
-        self.assertEqual(actual, self.crs)


=====================================
tests/test_features.py
=====================================
@@ -15,13 +15,13 @@ class FeaturesTest(unittest.TestCase):
         f = {
             'type': 'Feature',
             'id': '1',
-            'geometry': {'type': 'Point', 'coordinates': [53, -4]},
+            'geometry': {'type': 'Point', 'coordinates': [53.0, -4.0]},
             'properties': {'title': 'Dict 1'},
         }
 
         json = geojson.dumps(f, sort_keys=True)
         self.assertEqual(json, '{"geometry":'
-                               ' {"coordinates": [53, -4],'
+                               ' {"coordinates": [53.0, -4.0],'
                                ' "type": "Point"},'
                                ' "id": "1",'
                                ' "properties": {"title": "Dict 1"},'
@@ -30,7 +30,7 @@ class FeaturesTest(unittest.TestCase):
         o = geojson.loads(json)
         output = geojson.dumps(o, sort_keys=True)
         self.assertEqual(output, '{"geometry":'
-                                 ' {"coordinates": [53, -4],'
+                                 ' {"coordinates": [53.0, -4.0],'
                                  ' "type": "Point"},'
                                  ' "id": "1",'
                                  ' "properties": {"title": "Dict 1"},'
@@ -49,7 +49,7 @@ class FeaturesTest(unittest.TestCase):
         from geojson.examples import SimpleWebFeature
         feature = SimpleWebFeature(
             id='1',
-            geometry={'type': 'Point', 'coordinates': [53, -4]},
+            geometry={'type': 'Point', 'coordinates': [53.0, -4.0]},
             title='Feature 1', summary='The first feature',
             link='http://example.org/features/1'
         )
@@ -61,10 +61,10 @@ class FeaturesTest(unittest.TestCase):
         self.assertEqual(feature.properties['link'],
                          'http://example.org/features/1')
         self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True),
-                         '{"coordinates": [53, -4], "type": "Point"}')
+                         '{"coordinates": [53.0, -4.0], "type": "Point"}')
 
         # Encoding
-        json = ('{"geometry": {"coordinates": [53, -4],'
+        json = ('{"geometry": {"coordinates": [53.0, -4.0],'
                 ' "type": "Point"},'
                 ' "id": "1",'
                 ' "properties":'
@@ -77,7 +77,7 @@ class FeaturesTest(unittest.TestCase):
         # Decoding
         factory = geojson.examples.create_simple_web_feature
         json = ('{"geometry": {"type": "Point",'
-                ' "coordinates": [53, -4]},'
+                ' "coordinates": [53.0, -4.0]},'
                 ' "id": "1",'
                 ' "properties": {"summary": "The first feature",'
                 ' "link": "http://example.org/features/1",'
@@ -91,7 +91,7 @@ class FeaturesTest(unittest.TestCase):
         self.assertEqual(feature.properties['link'],
                          'http://example.org/features/1')
         self.assertEqual(geojson.dumps(feature.geometry, sort_keys=True),
-                         '{"coordinates": [53, -4], "type": "Point"}')
+                         '{"coordinates": [53.0, -4.0], "type": "Point"}')
 
     def test_geo_interface(self):
         class Thingy(object):
@@ -108,12 +108,12 @@ class FeaturesTest(unittest.TestCase):
                          "geometry": {"type": "Point",
                                       "coordinates": (self.x, self.y)}})
 
-        ob = Thingy('1', 'thingy one', -106, 40)
+        ob = Thingy('1', 'thingy one', -106.0, 40.0)
         self.assertEqual(geojson.dumps(ob.__geo_interface__['geometry'],
                                        sort_keys=True),
-                         '{"coordinates": [-106, 40], "type": "Point"}')
+                         '{"coordinates": [-106.0, 40.0], "type": "Point"}')
         self.assertEqual(geojson.dumps(ob, sort_keys=True),
-                         ('{"geometry": {"coordinates": [-106, 40],'
+                         ('{"geometry": {"coordinates": [-106.0, 40.0],'
                           ' "type": "Point"},'
                           ' "id": "1",'
                           ' "properties": {"title": "thingy one"}}'))


=====================================
tests/test_geo_interface.py
=====================================
@@ -65,21 +65,21 @@ class EncodingDecodingTest(unittest.TestCase):
                     properties={'name': self.name})
 
         self.name = "In N Out Burger"
-        self.latlng = [-54, 4]
+        self.latlng = [-54.0, 4.0]
 
         self.restaurant_nogeo = Restaurant(self.name, self.latlng)
 
         self.restaurant1 = Restaurant1(self.name, self.latlng)
         self.restaurant2 = Restaurant2(self.name, self.latlng)
 
-        self.restaurant_str = ('{"coordinates": [-54, 4],'
+        self.restaurant_str = ('{"coordinates": [-54.0, 4.0],'
                                ' "type": "Point"}')
 
         self.restaurant_feature1 = RestaurantFeature1(self.name, self.latlng)
         self.restaurant_feature2 = RestaurantFeature2(self.name, self.latlng)
 
         self.restaurant_feature_str = ('{"geometry":'
-                                       ' {"coordinates": [-54, 4],'
+                                       ' {"coordinates": [-54.0, 4.0],'
                                        ' "type": "Point"},'
                                        ' "properties":'
                                        ' {"name": "In N Out Burger"},'
@@ -133,15 +133,15 @@ class EncodingDecodingTest(unittest.TestCase):
 
     def test_invalid(self):
         with self.assertRaises(ValueError) as cm:
-            geojson.loads('{"type":"Point", "coordinates":[[-Infinity, 4]]}')
+            geojson.loads('{"type":"Point", "coordinates":[[-Infinity, 4.0]]}')
 
         self.assertIn('is not JSON compliant', str(cm.exception))
 
     def test_mapping(self):
-        self.assertEquals(to_mapping(geojson.Point([1, 2])),
-                          {"coordinates": [1, 2], "type": "Point"})
+        self.assertEqual(to_mapping(geojson.Point([1.0, 2.0])),
+                         {"coordinates": [1.0, 2.0], "type": "Point"})
 
     def test_GeoJSON(self):
-        self.assertEquals(None, geojson.GeoJSON().__geo_interface__)
+        self.assertEqual(None, geojson.GeoJSON().__geo_interface__)
 
-        self.assertEquals({"type": "GeoJSON"}, to_mapping(geojson.GeoJSON()))
+        self.assertEqual({"type": "GeoJSON"}, to_mapping(geojson.GeoJSON()))


=====================================
tox.ini
=====================================
@@ -4,7 +4,7 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py{27,33,34,35,36}, pypy, pypy3
+envlist = py{27,35,36,37}, pypy, pypy3
 
 [testenv]
 commands = {envpython} setup.py test



View it on GitLab: https://salsa.debian.org/debian-gis-team/python-geojson/commit/066d8e0935e413611f243139d6315f0f6eb35b33

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/python-geojson/commit/066d8e0935e413611f243139d6315f0f6eb35b33
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20190719/10d7ebee/attachment-0001.html>


More information about the Pkg-grass-devel mailing list