internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: Miserlou/Zappa

Issue:
zappa fails to properly invoke `pyenv help`
The invocation of `subprocess.check_output` is incorrect. 

It is either 

`subprocess.check_output('arbitrary shell commands', shell=True)`

OR

`subprocess.check_output(['command', 'args'])`

## Context

This is an issue because it causes an incorrect OSError exception, which causes zappa to miss the pyenv environment:

```
zappa deploy
Calling deploy for stage prod..
This directory seems to have pyenv's local venv, but pyenv executable was not found.
```

`Python 3.6.5`

## Expected Behavior
It should be able to detect `pyenv` via the execution of `pyenv help`

## Actual Behavior
It fails to detect `pyenv`, as the check_output call fails:

```
>>> subprocess.check_output('pyenv help')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/nick/.pyenv/versions/3.6.5/Python.framework/Versions/3.6/lib/python3.6/subprocess.py", line 336, in check_output
    **kwargs).stdout
  File "/Users/nick/.pyenv/versions/3.6.5/Python.framework/Versions/3.6/lib/python3.6/subprocess.py", line 403, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/Users/nick/.pyenv/versions/3.6.5/Python.framework/Versions/3.6/lib/python3.6/subprocess.py", line 709, in __init__
    restore_signals, start_new_session)
  File "/Users/nick/.pyenv/versions/3.6.5/Python.framework/Versions/3.6/lib/python3.6/subprocess.py", line 1344, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
FileNotFoundError: [Errno 2] No such file or directory: 'pyenv help': 'pyenv help'
```

## Possible Fix
The fix is to either invoke `check_output` with `shell=True` or as a list. 

PR coming...

## Steps to Reproduce
`zappa deploy`

## Your Environment
* Zappa version used: `0.46.1`
* Operating System and Python version: `Darwin x.local 17.6.0 Darwin Kernel Version 17.6.0: Tue May  8 15:22:16 PDT 2018; root:xnu-4570.61.1~1/RELEASE_X86_64 x86_64`
* Python version used: 3.6.5


Discussion:

Resolving patch:
diff --git a/zappa/core.py b/zappa/core.py
--- a/zappa/core.py
+++ b/zappa/core.py
@@ -433,7 +433,7 @@ def get_current_venv():
             venv = os.environ['VIRTUAL_ENV']
         elif os.path.exists('.python-version'):  # pragma: no cover
             try:
-                subprocess.check_output('pyenv help', stderr=subprocess.STDOUT)
+                subprocess.check_output(['pyenv', 'help'], stderr=subprocess.STDOUT)
             except OSError:
                 print("This directory seems to have pyenv's local venv, "
                       "but pyenv executable was not found.")


Test patch:
Repository: Miserlou/Zappa

Issue:
DynamoDB-based async never deletes the table
As seen in: https://github.com/Miserlou/Zappa/blob/master/zappa/core.py#L2740

The code doesn't work for it's purpose and seems to be an unmodified copy of the SNS subscription removal.


Discussion:

Resolving patch:
diff --git a/zappa/core.py b/zappa/core.py
--- a/zappa/core.py
+++ b/zappa/core.py
@@ -2741,14 +2741,7 @@ def remove_async_dynamodb_table(self, table_name):
         """
         Remove the DynamoDB Table used for async return values
         """
-
-        topic_name = get_topic_name(lambda_name)
-        removed_arns = []
-        for sub in self.sns_client.list_subscriptions()['Subscriptions']:
-            if topic_name in sub['TopicArn']:
-                self.sns_client.delete_topic(TopicArn=sub['TopicArn'])
-                removed_arns.append(sub['TopicArn'])
-        return removed_arns
+        self.dynamodb_client.delete_table(TableName=table_name)
 
     ##
     # CloudWatch Logging


Test patch:
Repository: Miserlou/Zappa

Issue:
Syntax warning due to comparison of literals using is in Python 3.8
## Context

Syntax warning due to comparison of literals using is.

## Possible Fix

Use == and != as suggested in the warning

## Steps to Reproduce

```
find . -iname '*.py'  | xargs -P 4 -I{} python -Walways -m py_compile {} 

./zappa/core.py:2026: SyntaxWarning: "is" with a literal. Did you mean "=="?
  elif key is 'LambdaConfig':
./zappa/cli.py:1379: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if token.count('-') is 4 and token.replace('-', '').isalnum():
./zappa/cli.py:2513: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if (token.count('.') is 3 and token.replace('.', '').isnumeric()):
./zappa/cli.py:2548: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if token.count('-') is 4 and token.replace('-', '').isalnum():
./zappa/cli.py:2555: SyntaxWarning: "is" with a literal. Did you mean "=="?
  if token.count('.') is 3 and token.replace('.', '').isnumeric():
./example/authmodule.py:78: DeprecationWarning: invalid escape sequence \*
  pathRegex = "^[/.a-zA-Z0-9-\*]+$"
```

## Your Environment
<!--- Include as many relevant details about the environment you experienced the bug in -->
* Zappa version used: master
* Operating System and Python version: Python 3.8
* The output of `pip freeze`:
* Link to your project (optional):
* Your `zappa_settings.json`: 



Discussion:

Resolving patch:
diff --git a/example/authmodule.py b/example/authmodule.py
--- a/example/authmodule.py
+++ b/example/authmodule.py
@@ -75,7 +75,7 @@ class AuthPolicy:
     """The principal used for the policy, this should be a unique identifier for the end user."""
     version = "2012-10-17"
     """The policy version used for the evaluation. This should always be '2012-10-17'"""
-    pathRegex = "^[/.a-zA-Z0-9-\*]+$"
+    pathRegex = r"^[/.a-zA-Z0-9-\*]+$"
     """The regular expression used to validate resource paths for the policy"""
 
     """these are the internal lists of allowed and denied methods. These are lists


Test patch:
Repository: Miserlou/Zappa

Issue:
zappa.io should redirect to www.zappa.io
going to zappa.io results in a DNS error.

www.zappa.io works fine.



Discussion:
Yeah, it should! This is really annoying, I thought I could do this in Route53 but it's giving me some guff.

Normally, it's better to stay off the apex domain, but Amazon seems to think the opposite. I might do this the other way round, so that www. points to the apex.

This looks like it can be closed.
It looks to still be an issue, at least one my end -

1. https://www.zappa.io/ - works
1. https://zappa.io/ - breaks
1. zappa.io/ - breaks
the naked domain (non-www) does not work for me either. If you are using Amazon's route53, then an A record to an Alias should work.

Resolving patch:
diff --git a/zappa/cli.py b/zappa/cli.py
--- a/zappa/cli.py
+++ b/zappa/cli.py
@@ -1799,7 +1799,7 @@ def init(self, settings_file="zappa_settings.json"):
         click.echo("\nTo learn more, check out our project page on " + click.style("GitHub", bold=True) +
                    " here: " + click.style("https://github.com/Miserlou/Zappa", fg="cyan", bold=True))
         click.echo("and stop by our " + click.style("Slack", bold=True) + " channel here: " +
-                   click.style("https://slack.zappa.io", fg="cyan", bold=True))
+                   click.style("https://zappateam.slack.com", fg="cyan", bold=True))
         click.echo("\nEnjoy!,")
         click.echo(" ~ Team " + click.style("Zappa", bold=True) + "!")
 
@@ -2751,7 +2751,7 @@ def shamelessly_promote():
     click.echo("File bug reports on " + click.style("GitHub", bold=True) + " here: "
                + click.style("https://github.com/Miserlou/Zappa", fg='cyan', bold=True))
     click.echo("And join our " + click.style("Slack", bold=True) + " channel here: "
-               + click.style("https://slack.zappa.io", fg='cyan', bold=True))
+               + click.style("https://zappateam.slack.com", fg='cyan', bold=True))
     click.echo("Love!,")
     click.echo(" ~ Team " + click.style("Zappa", bold=True) + "!")
 


Test patch:
Repository: graspologic-org/graspologic

Issue:
Fix notebooks on netlify
Issues with setting jupyter notebook kernels prevent them from running on netlify


Discussion:

Resolving patch:
diff --git a/graspy/embed/ase.py b/graspy/embed/ase.py
--- a/graspy/embed/ase.py
+++ b/graspy/embed/ase.py
@@ -41,8 +41,6 @@ class AdjacencySpectralEmbed(BaseEmbed):
         Number of iterations for randomized SVD solver. Not used by 'full' or 
         'truncated'. The default is larger than the default in randomized_svd 
         to handle sparse matrices that may have large slowly decaying spectrum.
-    lcc : bool, optional (default=True)
-        If True, computes the largest connected component for the input graph.
 
     Attributes
     ----------
diff --git a/graspy/embed/lse.py b/graspy/embed/lse.py
--- a/graspy/embed/lse.py
+++ b/graspy/embed/lse.py
@@ -40,8 +40,6 @@ class LaplacianSpectralEmbed(BaseEmbed):
         Number of iterations for randomized SVD solver. Not used by 'full' or 
         'truncated'. The default is larger than the default in randomized_svd 
         to handle sparse matrices that may have large slowly decaying spectrum.
-    lcc : bool, optional (default=True)
-        If True, computes the largest connected component for the input graph.
 
     Attributes
     ----------


Test patch:
Repository: graspologic-org/graspologic

Issue:
GClust bug
<img width="558" alt="Screen Shot 2019-06-22 at 3 46 06 PM" src="https://user-images.githubusercontent.com/25714207/59968259-eb346c80-9504-11e9-984c-8c13dff93a37.png">

should be `- self.min_components` rather than `- 1`

This causes an indexing error when `min_components` does not equal 1


Discussion:
Good catch. Will make a test for it as well.

Also just link the line directly in github rather than taking a screenshot

Resolving patch:
diff --git a/graspy/cluster/gclust.py b/graspy/cluster/gclust.py
--- a/graspy/cluster/gclust.py
+++ b/graspy/cluster/gclust.py
@@ -240,6 +240,6 @@ def fit(self, X, y=None):
 
         self.n_components_ = best_component
         self.covariance_type_ = best_covariance
-        self.model_ = models[best_component - 1][best_covariance_idx]
+        self.model_ = models[best_component - self.min_components][best_covariance_idx]
 
         return self


Test patch:
diff --git a/tests/cluster/test_gclust.py b/tests/cluster/test_gclust.py
--- a/tests/cluster/test_gclust.py
+++ b/tests/cluster/test_gclust.py
@@ -98,7 +98,7 @@ def test_no_y():
     assert_equal(gclust.n_components_, 2)
 
 
-def test_outputs():
+def test_two_class():
     """
     Easily separable two gaussian problem.
     """
@@ -127,7 +127,29 @@ def test_outputs():
         assert_allclose(gclust.ari_.loc[n_components], 1)
 
 
-def test_bic():
+def test_five_class():
+    """
+    Easily separable five gaussian problem.
+    """
+    np.random.seed(10)
+
+    n = 100
+    mus = [[i * 5, 0] for i in range(5)]
+    cov = np.eye(2)  # balls
+
+    num_sims = 10
+
+    for _ in range(num_sims):
+        X = np.vstack([np.random.multivariate_normal(mu, cov, n) for mu in mus])
+
+        gclust = GaussianCluster(
+            min_components=3, max_components=10, covariance_type="all"
+        )
+        gclust.fit(X)
+        assert_equal(gclust.n_components_, 5)
+
+
+def test_ase_three_blocks():
     """
     Expect 3 clusters from a 3 block model
     """
Repository: graspologic-org/graspologic

Issue:
Add binarize as a plotting transform
Make this one of the options for plotting with heatmap or gridplot


Discussion:

Resolving patch:
diff --git a/graspy/plot/plot.py b/graspy/plot/plot.py
--- a/graspy/plot/plot.py
+++ b/graspy/plot/plot.py
@@ -20,6 +20,7 @@
 import seaborn as sns
 from mpl_toolkits.axes_grid1 import make_axes_locatable
 from sklearn.utils import check_array, check_consistent_length
+from sklearn.preprocessing import Binarizer
 
 from ..embed import selectSVD
 from ..utils import import_graph, pass_to_ranks
@@ -104,8 +105,11 @@ def _transform(arr, method):
                 arr[arr > 0] = np.log10(arr[arr > 0])
         elif method in ["zero-boost", "simple-all", "simple-nonzero"]:
             arr = pass_to_ranks(arr, method=method)
+        elif method == "binarize":
+            transformer = Binarizer().fit(arr)
+            arr = transformer.transform(arr)
         else:
-            msg = "Transform must be one of {log, log10, zero-boost, simple-all, \
+            msg = "Transform must be one of {log, log10, binarize, zero-boost, simple-all, \
             simple-nonzero, not {}.".format(
                 method
             )
@@ -196,6 +200,8 @@ def heatmap(
         - 'simple-nonzero':
             Pass to ranks method. Same as simple-all, but ranks are scaled by
             :math:`\frac{rank(\text{non-zero edges})}{\text{# non-zero edges} + 1}`
+        - 'binarize':
+            Binarize input graph such that any edge weight greater than 0 becomes 1. 
 
     figsize : tuple of integers, optional, default: (10, 10)
         Width, height in inches.
@@ -394,6 +400,8 @@ def gridplot(
         - 'simple-nonzero':
             Pass to ranks method. Same as simple-all, but ranks are scaled by
             :math:`\frac{rank(\text{non-zero edges})}{\text{# non-zero edges} + 1}`
+        - 'binarize':
+            Binarize input graph such that any edge weight greater than 0 becomes 1. 
     height : int, optional, default: 10
         Height of figure in inches.
     title : str, optional, default: None


Test patch:
diff --git a/tests/test_plot.py b/tests/test_plot.py
--- a/tests/test_plot.py
+++ b/tests/test_plot.py
@@ -123,6 +123,7 @@ def test_heatmap_output():
     fig = heatmap(X, transform="zero-boost")
     fig = heatmap(X, transform="simple-all")
     fig = heatmap(X, transform="simple-nonzero")
+    fig = heatmap(X, transform="binarize")
     fig = heatmap(X, cmap="gist_rainbow")
Repository: graspologic-org/graspologic

Issue:
Pass arbitrary plotting arguments to heatmap. 
For example, be able to pass `annot = True` to `heatmap` function. Should be a quick fix.



Discussion:

Resolving patch:
diff --git a/graspy/plot/plot.py b/graspy/plot/plot.py
--- a/graspy/plot/plot.py
+++ b/graspy/plot/plot.py
@@ -162,6 +162,7 @@ def heatmap(
     ax=None,
     title_pad=None,
     sort_nodes=False,
+    **kwargs
 ):
     r"""
     Plots a graph as a color-encoded matrix.
@@ -250,6 +251,8 @@ def heatmap(
         Whether or not to sort the nodes of the graph by the sum of edge weights
         (degree for an unweighted graph). If ``inner_hier_labels`` is passed and 
         ``sort_nodes`` is ``True``, will sort nodes this way within block. 
+
+    **kwargs : additional plotting arguments passed to Seaborn's ``heatmap``
     """
     _check_common_inputs(
         figsize=figsize,
@@ -318,6 +321,7 @@ def heatmap(
             ax=ax,
             vmin=vmin,
             vmax=vmax,
+            **kwargs
         )
 
         if title is not None:


Test patch:
Repository: graspologic-org/graspologic

Issue:
make diagonal augmentation work for weighted graphs



Discussion:
https://graspy.neurodata.io/reference/utils.html#graspy.utils.augment_diagonal
When you say "work", would you like a particular operation done for each diagonal entries? Currently, it's just counting the nonzero entries and dividing them by (nverts-1), so would it suffice to change it by adding up the entries for weighted matrices and dividing them b y (nverts-1). Or for the purposes that this will used for, does taking a norm over the row make more sense?

Resolving patch:
diff --git a/graspy/utils/utils.py b/graspy/utils/utils.py
--- a/graspy/utils/utils.py
+++ b/graspy/utils/utils.py
@@ -563,18 +563,19 @@ def get_multigraph_intersect_lcc(graphs, return_inds=False):
 
 def augment_diagonal(graph, weight=1):
     r"""
-    Replaces the diagonal of adjacency matrix with 
-    :math:`\frac{degree}{nverts - 1}` for the degree associated
-    with each node. 
+    Replaces the diagonal of adjacency matrix with the sum of the edge weight.
+    This is equivalent to the degree for a weighted network.
 
     For directed graphs, the degree used is the out degree (number) of 
-    edges leaving the vertex. Ignores self-loops when calculating degree
+    edges leaving the vertex. Ignores self-loops when calculating degree.
 
     Parameters
     ----------
     graph: nx.Graph, nx.DiGraph, nx.MultiDiGraph, nx.MultiGraph, np.ndarray
         Input graph in any of the above specified formats. If np.ndarray, 
-        interpreted as an :math:`n \times n` adjacency matrix 
+        interpreted as an :math:`n \times n` adjacency matrix
+    weight: int
+         Weight to be applied to each index.
     
     Returns
     -------
@@ -597,7 +598,7 @@ def augment_diagonal(graph, weight=1):
     divisor = graph.shape[0] - 1
     # use out degree for directed graph
     # ignore self loops in either case
-    degrees = np.count_nonzero(graph, axis=1)
+    degrees = np.sum(graph, axis=1)
     diag = weight * degrees / divisor
     graph += np.diag(diag)
     return graph


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -362,9 +362,9 @@ def test_augment_diagonal_undirected(self):
         )
         expected = A.copy().astype(float)
         expected[0, 0] = 2.0 / 4
-        expected[1, 1] = 3.0 / 4
+        expected[1, 1] = 4.0 / 4
         expected[2, 2] = 3.0 / 4
-        expected[3, 3] = 2.0 / 4
+        expected[3, 3] = 3.0 / 4
         expected[4, 4] = 2.0 / 4
         A_aug = gus.augment_diagonal(A)
         np.testing.assert_array_equal(A_aug, expected)
@@ -381,9 +381,9 @@ def test_augment_diagonal_directed(self):
         )
         expected = A.copy().astype(float)
         expected[0, 0] = 2.0 / 4
-        expected[1, 1] = 2.0 / 4
+        expected[1, 1] = 3.0 / 4
         expected[2, 2] = 3.0 / 4
-        expected[3, 3] = 1.0 / 4
+        expected[3, 3] = 2.0 / 4
         expected[4, 4] = 1.0 / 4
         A_aug = gus.augment_diagonal(A)
         np.testing.assert_array_equal(A_aug, expected)
Repository: graspologic-org/graspologic

Issue:
consider having an option for SBM to return a label vector denoting the blocks
Since we changed the sorting to be by degree for heatmap/gridplot, everyone will have to make their own label vector to plot the blocks. Should have an option that just returns the labels for you 


Discussion:

Resolving patch:
diff --git a/graspy/simulations/simulations.py b/graspy/simulations/simulations.py
--- a/graspy/simulations/simulations.py
+++ b/graspy/simulations/simulations.py
@@ -18,6 +18,14 @@
 import warnings
 
 
+def _n_to_labels(n):
+    n_cumsum = n.cumsum()
+    labels = np.zeros(n.sum(), dtype=np.int64)
+    for i in range(1, len(n)):
+        labels[n_cumsum[i - 1] : n_cumsum[i]] = i
+    return labels
+
+
 def sample_edges(P, directed=False, loops=False):
     """
     Gemerates a binary random graph based on the P matrix provided
@@ -303,7 +311,17 @@ def er_nm(n, m, directed=False, loops=False, wt=1, wtargs=None):
     return A
 
 
-def sbm(n, p, directed=False, loops=False, wt=1, wtargs=None, dc=None, dc_kws={}):
+def sbm(
+    n,
+    p,
+    directed=False,
+    loops=False,
+    wt=1,
+    wtargs=None,
+    dc=None,
+    dc_kws={},
+    return_labels=False,
+):
     """
     Samples a graph from the stochastic block model (SBM). 
 
@@ -370,6 +388,11 @@ def sbm(n, p, directed=False, loops=False, wt=1, wtargs=None, dc=None, dc_kws={}
         If not specified, in either case all functions will assume their default
         parameters.
 
+    return_labels: boolean, optional (default=False)
+        If False, only output is adjacency matrix. Otherwise, an additional output will
+        be an array with length equal to the number of vertices in the graph, where each
+        entry in the array labels which block a vertex in the graph is in.
+
     References
     ----------
     .. [1] Tai Qin and Karl Rohe. "Regularized spectral clustering under the 
@@ -380,6 +403,8 @@ def sbm(n, p, directed=False, loops=False, wt=1, wtargs=None, dc=None, dc_kws={}
     -------
     A: ndarray, shape (sum(n), sum(n))
         Sampled adjacency matrix
+    labels: ndarray, shape (sum(n))
+        Label vector
 
     Examples
     --------
@@ -597,6 +622,9 @@ def sbm(n, p, directed=False, loops=False, wt=1, wtargs=None, dc=None, dc_kws={}
         A = A - np.diag(np.diag(A))
     if not directed:
         A = symmetrize(A, method="triu")
+    if return_labels:
+        labels = _n_to_labels(n)
+        return A, labels
     return A
 
 


Test patch:
diff --git a/tests/test_sims.py b/tests/test_sims.py
--- a/tests/test_sims.py
+++ b/tests/test_sims.py
@@ -356,6 +356,14 @@ def setUpClass(cls):
         cls.Psy = symmetrize(cls.Psy)
         cls.seed = 12345
 
+    def test_sbm_label(self):
+        np.random.seed(1)
+        n = [3, 3]
+        p = [[0.5, 0.1], [0.1, 0.5]]
+        A, l = sbm(n, p, return_labels=True)
+        label = [0, 0, 0, 1, 1, 1]
+        self.assertTrue(np.allclose(l, label))
+
     def test_sbm(self):
         n = [50, 60, 70]
         vcount = np.cumsum(n)
Repository: graspologic-org/graspologic

Issue:
deprecate python 3.5
sklearn 0.23 and above don't support 3.5


Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@
 AUTHOR = ("Eric Bridgeford, Jaewon Chung, Benjamin Pedigo, Bijan Varjavand",)
 AUTHOR_EMAIL = "j1c@jhu.edu"
 URL = "https://github.com/neurodata/graspy"
-MINIMUM_PYTHON_VERSION = 3, 5  # Minimum of Python 3.5
+MINIMUM_PYTHON_VERSION = 3, 6  # Minimum of Python 3.5
 REQUIRED_PACKAGES = [
     "networkx>=2.1",
     "numpy>=1.8.1",
@@ -52,7 +52,6 @@ def check_python_version():
         "Topic :: Scientific/Engineering :: Mathematics",
         "License :: OSI Approved :: Apache Software License",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
     ],


Test patch:
Repository: graspologic-org/graspologic

Issue:
Change default for MASE
<!--
The issue tracker is a tool to address bugs in GraSPy itself. If you'd like to report a bug in GraSPy, fill out the template below and provide any extra information that may be useful / related to your problem.
-->
Make `scaled = True` as default for MASE


Discussion:

Resolving patch:
diff --git a/graspy/embed/mase.py b/graspy/embed/mase.py
--- a/graspy/embed/mase.py
+++ b/graspy/embed/mase.py
@@ -66,7 +66,7 @@ class MultipleASE(BaseEmbedMulti):
         'truncated'. The default is larger than the default in randomized_svd 
         to handle sparse matrices that may have large slowly decaying spectrum.
 
-    scaled : bool, optional (default=False)
+    scaled : bool, optional (default=True)
         Whether to scale individual eigenvectors with eigenvalues in first embedding 
         stage.
 
@@ -100,7 +100,7 @@ def __init__(
         n_elbows=2,
         algorithm="randomized",
         n_iter=5,
-        scaled=False,
+        scaled=True,
     ):
         if not isinstance(scaled, bool):
             msg = "scaled must be a boolean, not {}".format(scaled)


Test patch:
Repository: graspologic-org/graspologic

Issue:
GraphMatch should inherit from BaseEstimator



Discussion:
I'll grab this one

Resolving patch:
diff --git a/graspy/match/gmp.py b/graspy/match/gmp.py
--- a/graspy/match/gmp.py
+++ b/graspy/match/gmp.py
@@ -16,12 +16,13 @@
 import math
 from scipy.optimize import linear_sum_assignment
 from scipy.optimize import minimize_scalar
+from sklearn.base import BaseEstimator
 from sklearn.utils import check_array
 from sklearn.utils import column_or_1d
 from .skp import SinkhornKnopp
 
 
-class GraphMatch:
+class GraphMatch(BaseEstimator):
     """
     This class solves the Graph Matching Problem and the Quadratic Assignment Problem
     (QAP) through an implementation of the Fast Approximate QAP Algorithm (FAQ) (these


Test patch: