Repository: dmlc/dgl

Issue:
Error should be raised if aggregator_type provided is outside allowed values
## 🐛 Bug

As per title. [SAGEConv](https://docs.dgl.ai/api/python/nn.pytorch.html#sageconv) is missing some user input sanitization for the `aggregator_type` parameter, which can be error prone, e.g. if user enters in `max_pool` instead of `pool` as expected by SAGEConv.

## To Reproduce

Steps to reproduce the behavior:

1. Instantiate a SAGEConv layer with `aggregator_type` = 'foo'. 

```python
conv = SAGEConv(10, 2, aggregator_type='foo')
```
No errors raised.

## Expected behavior

A DGLError should be raised, similar to how if the user provides a `norm` value that is outside of the allowed values (right, none, both, left) in GraphConv, see [here](https://github.com/dmlc/dgl/blob/master/python/dgl/nn/pytorch/conv/graphconv.py#L281)

## Environment

 - DGL Version (e.g., 1.0): 0.7.1
 - Backend Library & Version (e.g., PyTorch 0.4.1, MXNet/Gluon 1.3): pytorch 1.9.1+cpu
 - OS (e.g., Linux): macos
 - How you installed DGL (`conda`, `pip`, source): pip
 - Build command you used (if compiling from source):
 - Python version: 3.6.8
 - CUDA/cuDNN version (if applicable):
 - GPU models and configuration (e.g. V100):
 - Any other relevant information:

## Additional context

I will also note that in the User Guide, https://docs.dgl.ai/guide/nn-construction.html, there *is* this check for the aggregator_type in the code samples. So I find it strange it is not in the code base. (Moreover, the listed allowed values are `['mean', 'max_pool', 'lstm', 'gcn']` there.)

I'll be happy to send in a PR if I receive confirmation that this is something the authors wish to fix. Thanks.


**Edit:** I just realized a KeyError is thrown during `forward()`, but I think throwing an error earlier during the init is appropriate as well.



Discussion:
Thanks. Could you open a pull request for this fix?
Sure, will do. Thanks for the feedback.

Resolving patch:
diff --git a/python/dgl/nn/mxnet/conv/sageconv.py b/python/dgl/nn/mxnet/conv/sageconv.py
--- a/python/dgl/nn/mxnet/conv/sageconv.py
+++ b/python/dgl/nn/mxnet/conv/sageconv.py
@@ -6,6 +6,7 @@
 from mxnet.gluon import nn
 
 from .... import function as fn
+from ....base import DGLError
 from ....utils import expand_as_pair, check_eq_shape
 
 class SAGEConv(nn.Block):
@@ -101,6 +102,12 @@ def __init__(self,
                  norm=None,
                  activation=None):
         super(SAGEConv, self).__init__()
+        valid_aggre_types = {'mean', 'gcn', 'pool', 'lstm'}
+        if aggregator_type not in valid_aggre_types:
+            raise DGLError(
+                'Invalid aggregator_type. Must be one of {}. '
+                'But got {!r} instead.'.format(valid_aggre_types, aggregator_type)
+            )
 
         self._in_src_feats, self._in_dst_feats = expand_as_pair(in_feats)
         self._out_feats = out_feats
diff --git a/python/dgl/nn/pytorch/conv/sageconv.py b/python/dgl/nn/pytorch/conv/sageconv.py
--- a/python/dgl/nn/pytorch/conv/sageconv.py
+++ b/python/dgl/nn/pytorch/conv/sageconv.py
@@ -5,6 +5,7 @@
 from torch.nn import functional as F
 
 from .... import function as fn
+from ....base import DGLError
 from ....utils import expand_as_pair, check_eq_shape, dgl_warning
 
 
@@ -106,6 +107,12 @@ def __init__(self,
                  norm=None,
                  activation=None):
         super(SAGEConv, self).__init__()
+        valid_aggre_types = {'mean', 'gcn', 'pool', 'lstm'}
+        if aggregator_type not in valid_aggre_types:
+            raise DGLError(
+                'Invalid aggregator_type. Must be one of {}. '
+                'But got {!r} instead.'.format(valid_aggre_types, aggregator_type)
+            )
 
         self._in_src_feats, self._in_dst_feats = expand_as_pair(in_feats)
         self._out_feats = out_feats
diff --git a/python/dgl/nn/tensorflow/conv/sageconv.py b/python/dgl/nn/tensorflow/conv/sageconv.py
--- a/python/dgl/nn/tensorflow/conv/sageconv.py
+++ b/python/dgl/nn/tensorflow/conv/sageconv.py
@@ -4,6 +4,7 @@
 from tensorflow.keras import layers
 
 from .... import function as fn
+from ....base import DGLError
 from ....utils import expand_as_pair, check_eq_shape
 
 
@@ -100,6 +101,12 @@ def __init__(self,
                  norm=None,
                  activation=None):
         super(SAGEConv, self).__init__()
+        valid_aggre_types = {'mean', 'gcn', 'pool', 'lstm'}
+        if aggregator_type not in valid_aggre_types:
+            raise DGLError(
+                'Invalid aggregator_type. Must be one of {}. '
+                'But got {!r} instead.'.format(valid_aggre_types, aggregator_type)
+            )
 
         self._in_src_feats, self._in_dst_feats = expand_as_pair(in_feats)
         self._out_feats = out_feats


Test patch: