Repository: aplbrain/grand (language: python)

Issue:
Tightly pinned versions in requirements
Hi, I was trying to use the project. But the tight pins on the requirements, seems to be hindering me from installing it in my virtualenv.

https://github.com/aplbrain/grand/blob/master/setup.py#L18

Can we reduce these constrains to >= so that it's clearer what the minimum requirements are ?

Some notes:
 - I still need to support pandas 0.25
 - Support for lower numpy versions like 1.11 is generally preferred as conda will use that by default
 - SQLAlchemy 1.4 is what I'm using right now - but a bunch of folks are using 1.3 also. There are some backward incompatible changes between them
 - network - I try to use the latest. But am open to trying older versions if needed



Discussion / hints:
Oh, also - boto3 is prohibited for me as I am not allowed to use AWS resources for some of my projects
Sqlalchemy and boto3 should be extra dependencies maybe
@AbdealiJK — thank you for reporting! 

Yes, these package restrictions are a bit too tight, and I really should separate the "cloud" flavored requirements out into `grand-graph[dynamodb]` or something... this week is crazy busy for me but I'll try to find time later this week or next weekend to address this! In the meantime, if you happen to work out a working package requirement list, i'd appreciate it :)

To make this work, I think we'll need to both separate the install reqs into extra-deps, and then also make sure that we don't have any imports anywhere (including in auto-running unit tests...) that depend on them...

Fix patch:
diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml
index 661fb3f..4c34f47 100644
--- a/.github/workflows/python-package.yml
+++ b/.github/workflows/python-package.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8]
+        python-version: [3.6, 3.7, 3.8, 3.9]
 
     steps:
     - uses: actions/checkout@v2
@@ -27,7 +27,7 @@ jobs:
       run: |
         python -m pip install --upgrade pip
         pip install flake8 pytest
-        if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+        pip install -e ".[sql]"
     - name: Lint with flake8
       run: |
         # stop the build if there are Python syntax errors or undefined names
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 983402c..7ee010a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,13 @@
 
 ## **0.3.0**
 
-> This version adds support for Gremlin-compatible graph databases, such as AWS Neptune, TinkerPop, Janus, etc, through the `GremlinBackend`.
+> This version adds support for Gremlin-compatible graph databases, such as AWS Neptune, TinkerPop, Janus, etc, through the `GremlinBackend`, and loosens the requirements for the base installation of `grand-graph`. You can now install `grand-graph[sql]` or `grand-graph[dynamodb]` to get additional functionality (with additional dependencies).
 
 -   Improvements
     -   Backends
         -   Add `GremlinBackend` to the list of supported backends
+-   Housekeeping
+    -   Removes sqlalchemy and boto3 from the list of requirements for the base install. You can now install these with `pip3 install grand-graph[sql]` or `[dyanmodb]`.
 
 ## **0.2.0**
 
diff --git a/grand/__init__.py b/grand/__init__.py
index e62be66..01a8f3e 100644
--- a/grand/__init__.py
+++ b/grand/__init__.py
@@ -10,7 +10,7 @@
 
 _DEFAULT_BACKEND = NetworkXBackend
 
-__version__ = "0.2.0"
+__version__ = "0.2.1"
 
 
 class Graph:
diff --git a/grand/backends/__init__.py b/grand/backends/__init__.py
index e66c29c..25be9d8 100644
--- a/grand/backends/__init__.py
+++ b/grand/backends/__init__.py
@@ -1,6 +1,17 @@
 from .backend import Backend
-from .dynamodb import DynamoDBBackend
+
+try:
+    from .dynamodb import DynamoDBBackend
+except ImportError:
+    pass
 from .networkx import NetworkXBackend
-from .sqlbackend import SQLBackend
 
-# from .networkit import NetworkitBackend
+try:
+    from .sqlbackend import SQLBackend
+except ImportError:
+    pass
+
+try:
+    from .networkit import NetworkitBackend
+except ImportError:
+    pass
diff --git a/grand/backends/networkx.py b/grand/backends/networkx.py
index 1368682..e2a9cf9 100644
--- a/grand/backends/networkx.py
+++ b/grand/backends/networkx.py
@@ -1,7 +1,6 @@
 from typing import Hashable, Generator, Iterable
 import time
 
-import boto3
 import pandas as pd
 import networkx as nx
 
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 015a888..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-boto3
-networkx==2.4
-numpy==1.19.1
-pandas==1.1.0
-SQLAlchemy==1.3.18
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 197985d..4cf7659 100644
--- a/setup.py
+++ b/setup.py
@@ -3,11 +3,10 @@
 with open("README.md", "r") as fh:
     long_description = fh.read()
 
-["sqlalchemy"]
 
 setuptools.setup(
     name="grand-graph",
-    version="0.2.0",
+    version="0.2.1",
     author="Jordan Matelsky",
     author_email="opensource@matelsky.com",
     description="Graph database wrapper for non-graph datastores",
@@ -16,12 +15,14 @@
     url="https://github.com/aplbrain/grand",
     packages=setuptools.find_packages(),
     install_requires=[
-        "boto3",
-        "networkx==2.4",
-        "numpy==1.19.1",
-        "pandas==1.1.0",
-        "SQLAlchemy==1.3.18",
+        "networkx>=2.4",
+        "numpy",
+        "pandas",
     ],
+    extra_requires={
+        "sql": ["SQLAlchemy>=1.3"],
+        "dynamodb": ["boto3"],
+    },
     classifiers=[
         "Programming Language :: Python :: 3",
         "License :: OSI Approved :: MIT License",


Test patch:
diff --git a/grand/backends/test_backends.py b/grand/backends/test_backends.py
index a0e9612..f1217d7 100644
--- a/grand/backends/test_backends.py
+++ b/grand/backends/test_backends.py
@@ -3,9 +3,25 @@
 
 import networkx as nx
 
-from . import NetworkXBackend, SQLBackend, DynamoDBBackend
+from . import NetworkXBackend
+
+try:
+    from .dynamodb import DynamoDBBackend
+
+    _CAN_IMPORT_DYNAMODB = True
+except ImportError:
+    _CAN_IMPORT_DYNAMODB = False
+
+try:
+    from .sqlbackend import SQLBackend
+
+    _CAN_IMPORT_SQL = True
+except ImportError:
+    _CAN_IMPORT_SQL = False
+
 from .. import Graph
 
+
 backend_test_params = [
     pytest.param(
         NetworkXBackend,
@@ -14,21 +30,30 @@
             reason="NetworkX Backend skipped because $TEST_NETWORKXBACKEND != 0.",
         ),
     ),
-    pytest.param(
-        SQLBackend,
-        marks=pytest.mark.skipif(
-            os.environ.get("TEST_SQLBACKEND", default="1") != "1",
-            reason="SQL Backend skipped because $TEST_SQLBACKEND != 0.",
+]
+
+if _CAN_IMPORT_DYNAMODB:
+    backend_test_params.append(
+        pytest.param(
+            DynamoDBBackend,
+            marks=pytest.mark.skipif(
+                os.environ.get("TEST_DYNAMODB", default="1") != "1",
+                reason="DynamoDB Backend skipped because $TEST_DYNAMODB != 0 or boto3 is not installed",
+            ),
         ),
-    ),
-    pytest.param(
-        DynamoDBBackend,
-        marks=pytest.mark.skipif(
-            os.environ.get("TEST_DYNAMODBBACKEND") != "1",
-            reason="DynamoDB Backend skipped because $TEST_DYNAMODBBACKEND != 0.",
+    )
+
+if _CAN_IMPORT_SQL:
+    backend_test_params.append(
+        pytest.param(
+            SQLBackend,
+            marks=pytest.mark.skipif(
+                os.environ.get("TEST_SQLBACKEND", default="1") != "1"
+                or not _CAN_IMPORT_SQL,
+                reason="SQL Backend skipped because $TEST_SQLBACKEND != 0 or sqlalchemy is not installed.",
+            ),
         ),
-    ),
-]
+    )
 
 if os.environ.get("TEST_NETWORKITBACKEND") == "1":
     from .networkit import NetworkitBackend
@@ -93,7 +118,7 @@ def test_can_get_node(self, backend):
     def test_can_get_edge(self, backend):
         G = Graph(backend=backend())
         nxG = nx.Graph()
-        md = {"k":"B"}
+        md = {"k": "B"}
         G.nx.add_edge("A", "B", **md)
         nxG.add_edge("A", "B", **md)
         assert G.nx.get_edge_data("A", "B") == nxG.get_edge_data("A", "B")
@@ -130,7 +155,7 @@ def test_undirected_adj(self, backend):
         assert G.nx._adj == nxG._adj
 
     def test_directed_adj(self, backend):
-        G = Graph(backend=SQLBackend(directed=True))
+        G = Graph(backend=backend(directed=True))
         nxG = nx.DiGraph()
         assert G.nx._adj == nxG._adj
         G.nx.add_edge("A", "B")