Repository: poliastro/poliastro

Issue:
Try to compile with MyPy
"Mypy is an experimental optional static type checker for Python that aims to combine the benefits of dynamic (or "duck") typing and static typing". A new version is out:

http://mypy-lang.blogspot.com.es/2017/05/mypy-0510-released.html

The first question is: does this make any sense? The only way to find out is to try it at least once and decide.

Adding stubs might be needed to improve editor type hints and catch bugs:

https://www.caktusgroup.com/blog/2017/02/22/python-type-annotations/

For external libraries, there's `typeshed` but it includes none of our core dependencies:

https://github.com/python/typeshed

Though this might be a good starting point

https://github.com/machinalis/mypy-data

This is a very, very good article on good practices:

http://blog.zulip.org/2016/10/13/static-types-in-python-oh-mypy/

Ideally this would also be integrated in continuous integration, though I don't know how.

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index fd235711..769e58c4 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,9 @@ branches:
     - 0.5.x
     - 0.6.x
 
+env:
+  - MPLBACKEND="Agg"
+
 matrix:
   include:
     - os: osx
@@ -34,12 +37,17 @@ before_install:
 
 install:
   - pip install numpy  # Required
-  - pip install .  # Test installation correctness
+  - pip install pep8 mypy
   - pip install sphinx sphinx_rtd_theme nbsphinx ipython  # Install documentation dependencies
+  - pip install .  # Test installation correctness
+
+before_script:
+  - pep8 setup.py src/  # Check PEP8 compliance
+  - mypy --ignore-missing-imports --check-untyped-defs src/  # Check MyPy diagnostics
 
 script:
   - python setup.py test -vv  # Test against installed code
-  - sphinx-build -vW -b html docs/source _HTMLTest #Test docs build
+  - sphinx-build -vW -b html docs/source _HTMLTest  # Test docs build
 
 after_success:
   # Uninstall to test coverage against sources
diff --git a/setup.cfg b/setup.cfg
index 0e1fa302..06b96ece 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,3 +6,7 @@ norecursedirs =
 python_files =
     test_*.py
 doctest_plus = disabled
+
+[pep8]
+ignore = E731
+max-line-length = 120
diff --git a/setup.py b/setup.py
index 20ff6ca8..a17e69c4 100644
--- a/setup.py
+++ b/setup.py
@@ -40,8 +40,8 @@ setup(
     download_url="https://github.com/poliastro/poliastro",
     license="MIT",
     keywords=[
-      "aero", "aerospace", "engineering",
-      "astrodynamics", "orbits", "kepler", "orbital mechanics"
+        "aero", "aerospace", "engineering",
+        "astrodynamics", "orbits", "kepler", "orbital mechanics"
     ],
     python_requires=">=3.5",
     install_requires=[
@@ -64,24 +64,24 @@ setup(
     packages=find_packages('src'),
     package_dir={'': 'src'},
     entry_points={
-              'console_scripts': [
-                  'poliastro = poliastro.cli:main'
-              ]
+        'console_scripts': [
+            'poliastro = poliastro.cli:main'
+        ]
     },
     classifiers=[
-      "Development Status :: 4 - Beta",
-      "Intended Audience :: Education",
-      "Intended Audience :: Science/Research",
-      "License :: OSI Approved :: MIT License",
-      "Operating System :: OS Independent",
-      "Programming Language :: Python",
-      "Programming Language :: Python :: 3",
-      "Programming Language :: Python :: 3.5",
-      "Programming Language :: Python :: 3.6",
-      "Programming Language :: Python :: Implementation :: CPython",
-      "Topic :: Scientific/Engineering",
-      "Topic :: Scientific/Engineering :: Physics",
-      "Topic :: Scientific/Engineering :: Astronomy",
+        "Development Status :: 4 - Beta",
+        "Intended Audience :: Education",
+        "Intended Audience :: Science/Research",
+        "License :: OSI Approved :: MIT License",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: 3.5",
+        "Programming Language :: Python :: 3.6",
+        "Programming Language :: Python :: Implementation :: CPython",
+        "Topic :: Scientific/Engineering",
+        "Topic :: Scientific/Engineering :: Physics",
+        "Topic :: Scientific/Engineering :: Astronomy",
     ],
     long_description=io.open('README.rst', encoding='utf-8').read(),
     package_data={"poliastro": ['tests/*.py']},
diff --git a/src/poliastro/cli.py b/src/poliastro/cli.py
index 03b66b96..ce09ab62 100644
--- a/src/poliastro/cli.py
+++ b/src/poliastro/cli.py
@@ -14,21 +14,12 @@ def main():
         description="Command line tools for the poliastro Python library.")
     parser.add_argument("--version", action='version',
                         version=poliastro.__version__)
-
-    subparsers = parser.add_subparsers(help='Available commands')
-
-    download_parser = subparsers.add_parser(
-        "download-dastcom5",
-        help="Downloads DASTCOM5 database")
-    download_parser.add_argument(
-        "-p", "--path", help="Download path")
+    parser.add_argument("--download-dastcom5", action="store_true",
+                        help="Downloads DASTCOM5 database")
 
     args = parser.parse_args()
-    try:
-        if args.path:
-            download_dastcom5(args.path)
-        else:
-            download_dastcom5()
-    except AttributeError:
-        parser.print_help()
 
+    if args.download_dastcom5:
+        download_dastcom5()
+    else:
+        parser.print_help()
diff --git a/src/poliastro/maneuver.py b/src/poliastro/maneuver.py
index 95bf6436..8340bf50 100644
--- a/src/poliastro/maneuver.py
+++ b/src/poliastro/maneuver.py
@@ -93,7 +93,7 @@ class Maneuver(object):
         Rs = r_b / r_i
         dv_a = ((np.sqrt(2 * Rs / (1 + Rs)) - 1) * v_i).decompose()
         dv_b = (-np.sqrt(2 / Rs) * (np.sqrt(1 / (1 + Rs / R)) -
-                                   np.sqrt(1 / (1 + Rs))) * v_i).decompose()
+                                    np.sqrt(1 / (1 + Rs))) * v_i).decompose()
         dv_c = (-(np.sqrt(2 * Rs / (R + Rs)) - 1) / np.sqrt(R) * v_i).decompose()
         t_trans1 = (np.pi * np.sqrt((r_i * (1 + Rs) / 2) ** 3 / k)).decompose()
         t_trans2 = (np.pi * np.sqrt((r_i * (R + Rs) / 2) ** 3 / k)).decompose()
diff --git a/src/poliastro/neos/dastcom5.py b/src/poliastro/neos/dastcom5.py
index 701dc367..b50d1bfc 100644
--- a/src/poliastro/neos/dastcom5.py
+++ b/src/poliastro/neos/dastcom5.py
@@ -506,8 +506,8 @@ def download_dastcom5():
 
 
 def _show_download_progress(transferred, block, totalsize):
-    trans_mb = transferred*block/(1024*1024)
-    total_mb = totalsize/(1024*1024)
+    trans_mb = transferred * block / (1024 * 1024)
+    total_mb = totalsize / (1024 * 1024)
     print('%.2f MB / %.2f MB' % (trans_mb, total_mb), end='\r', flush=True)
 
 
@@ -526,13 +526,13 @@ def entire_db():
     ast_database = asteroid_db()
     com_database = comet_db()
 
-    ast_database = pd.DataFrame(ast_database[list(ast_database.dtype.names[:17])
-                                             + list(ast_database.dtype.names[-4:-3])
-                                             + list(ast_database.dtype.names[-2:])])
+    ast_database = pd.DataFrame(ast_database[list(ast_database.dtype.names[:17]) +
+                                             list(ast_database.dtype.names[-4:-3]) +
+                                             list(ast_database.dtype.names[-2:])])
     ast_database.rename(columns={'ASTNAM': 'NAME', 'NO': 'NUMBER', 'CALEPO': 'CALEPOCH'}, inplace=True)
-    com_database = pd.DataFrame(com_database[list(com_database.dtype.names[:17])
-                                             + list(com_database.dtype.names[-4:-3])
-                                             + list(com_database.dtype.names[-2:])])
+    com_database = pd.DataFrame(com_database[list(com_database.dtype.names[:17]) +
+                                             list(com_database.dtype.names[-4:-3]) +
+                                             list(com_database.dtype.names[-2:])])
     com_database.rename(columns={'COMNAM': 'NAME', 'NO': 'NUMBER', 'CALEPO': 'CALEPOCH'}, inplace=True)
     df = ast_database.append(com_database, ignore_index=True)
     df[['NAME', 'DESIG', 'IREF']] = df[['NAME', 'DESIG', 'IREF']].apply(lambda x: x.str.strip().str.decode('utf-8'))
diff --git a/src/poliastro/neos/neows.py b/src/poliastro/neos/neows.py
index 25b2d265..0c551ca9 100644
--- a/src/poliastro/neos/neows.py
+++ b/src/poliastro/neos/neows.py
@@ -37,7 +37,7 @@ def orbit_from_spk_id(spk_id):
         NEA orbit.
 
     """
-    payload = {'api_key' : 'DEMO_KEY'}
+    payload = {'api_key': 'DEMO_KEY'}
 
     response = requests.get(NEOWS_URL + spk_id, params=payload)
     response.raise_for_status()
@@ -55,7 +55,7 @@ def orbit_from_spk_id(spk_id):
     epoch = Time(orbital_data['orbit_determination_date'])
 
     return Orbit.from_classical(attractor, a, ecc, inc,
-                                    raan, argp, nu, epoch)
+                                raan, argp, nu, epoch)
 
 
 def spk_id_from_name(name):
@@ -102,8 +102,8 @@ def spk_id_from_name(name):
             bodies += body.string + '\n'
         raise ValueError(str(len(object_list)) + ' different bodies found:\n' + bodies)
     else:
-        raise ValueError('Object could not be found. You can visit: '
-                         + SBDB_URL + '?sstr=' + name + ' for more information.')
+        raise ValueError('Object could not be found. You can visit: ' +
+                         SBDB_URL + '?sstr=' + name + ' for more information.')
 
 
 def orbit_from_name(name):
diff --git a/src/poliastro/plotting.py b/src/poliastro/plotting.py
index 6e02b4cf..1472b294 100644
--- a/src/poliastro/plotting.py
+++ b/src/poliastro/plotting.py
@@ -2,7 +2,6 @@
 """ Plotting utilities.
 
 """
-
 import numpy as np
 
 import matplotlib as mpl
@@ -58,7 +57,6 @@ class OrbitPlotter(object):
             _, self.ax = plt.subplots(figsize=(6, 6))
         self.num_points = num_points
         self._frame = None
-        self._states = []
         self._attractor_radius = None
 
     def set_frame(self, p_vec, q_vec, w_vec):
@@ -119,8 +117,6 @@ class OrbitPlotter(object):
         elif new_radius < self._attractor_radius:
             self.set_attractor(orbit)
 
-        self._states.append(orbit)
-
         lines = []
 
         nu_vals = self._generate_vals(orbit.state)
diff --git a/src/poliastro/twobody/classical.py b/src/poliastro/twobody/classical.py
index 7e661a76..b01fa9a9 100644
--- a/src/poliastro/twobody/classical.py
+++ b/src/poliastro/twobody/classical.py
@@ -151,8 +151,8 @@ class ClassicalState(BaseState):
                       self.nu.to(u.rad).value)
 
         return poliastro.twobody.rv.RVState(self.attractor,
-                                                        r * u.km,
-                                                        v * u.km / u.s)
+                                            r * u.km,
+                                            v * u.km / u.s)
 
     def to_classical(self):
         """Converts to classical orbital elements representation.
diff --git a/src/poliastro/twobody/decorators.py b/src/poliastro/twobody/decorators.py
index 87c6a956..0782dd76 100644
--- a/src/poliastro/twobody/decorators.py
+++ b/src/poliastro/twobody/decorators.py
@@ -18,7 +18,7 @@ def state_from_vector(func):
     @wraps(func)
     def wrapper(t, u_, k, *args, **kwargs):
         r, v = u_[:3], u_[3:]
-        ss = RVState(Body(k * u.km3s2), r * u.km, v * u.kms)
+        ss = RVState(Body(None, k * u.km3s2, "_Dummy"), r * u.km, v * u.kms)
 
         return func(t, ss, *args, **kwargs)
 
diff --git a/src/poliastro/twobody/orbit.py b/src/poliastro/twobody/orbit.py
index d76b6dc8..2143823f 100644
--- a/src/poliastro/twobody/orbit.py
+++ b/src/poliastro/twobody/orbit.py
@@ -183,10 +183,10 @@ class Orbit(object):
         k = attractor.k.to(u.km ** 3 / u.s ** 2)
         ecc = 1.0 * u.one
         r, v = poliastro.twobody.classical.coe2rv(
-                k.to(u.km ** 3 / u.s ** 2).value,
-                p.to(u.km).value, ecc.value, inc.to(u.rad).value,
-                raan.to(u.rad).value, argp.to(u.rad).value,
-                nu.to(u.rad).value)
+            k.to(u.km ** 3 / u.s ** 2).value,
+            p.to(u.km).value, ecc.value, inc.to(u.rad).value,
+            raan.to(u.rad).value, argp.to(u.rad).value,
+            nu.to(u.rad).value)
 
         ss = cls.from_vectors(attractor, r * u.km, v * u.km / u.s, epoch)
         return ss
diff --git a/src/poliastro/twobody/rv.py b/src/poliastro/twobody/rv.py
index bbb83259..c9f3e50e 100644
--- a/src/poliastro/twobody/rv.py
+++ b/src/poliastro/twobody/rv.py
@@ -99,10 +99,11 @@ class RVState(BaseState):
 
         a = p / (1 - ecc**2)
 
-        return poliastro.twobody.classical.ClassicalState(self.attractor,
-                                                   a * u.km,
-                                                   ecc * u.one,
-                                                   inc * u.rad,
-                                                   raan * u.rad,
-                                                   argp * u.rad,
-                                                   nu * u.rad)
+        return poliastro.twobody.classical.ClassicalState(
+            self.attractor,
+            a * u.km,
+            ecc * u.one,
+            inc * u.rad,
+            raan * u.rad,
+            argp * u.rad,
+            nu * u.rad)


Test patch:
diff --git a/src/poliastro/neos/tests/test_dastcom5.py b/src/poliastro/neos/tests/test_dastcom5.py
index 0de46e1a..f94a7f52 100644
--- a/src/poliastro/neos/tests/test_dastcom5.py
+++ b/src/poliastro/neos/tests/test_dastcom5.py
@@ -32,7 +32,6 @@ def test_orbit_from_name(mock_record_from_name, mock_orbit_from_record):
     mock_record_from_name.assert_called_with(name)
 
 
-
 @mock.patch('poliastro.neos.dastcom5.orbit_from_record')
 @mock.patch('poliastro.neos.dastcom5.record_from_name')
 def test_record_from_name(mock_record_from_name, mock_orbit_from_record):
@@ -56,14 +55,15 @@ def test_read_headers(mock_open, mock_np_fromfile):
 @mock.patch('poliastro.neos.dastcom5.np.fromfile')
 @mock.patch('poliastro.neos.dastcom5.open')
 def test_read_record(mock_open, mock_np_fromfile, mock_read_headers):
-    mocked_ast_headers = np.array([(3184, -1, b'00740473', b'00496815')],
-      dtype=[('IBIAS1', np.int32), ('IBIAS0', np.int32), ('ENDPT2', '|S8'), ('ENDPT1', '|S8')])
-    mocked_com_headers = np.array([(99999,)],  dtype=[('IBIAS2', '<i4')])
+    mocked_ast_headers = np.array(
+        [(3184, -1, b'00740473', b'00496815')],
+        dtype=[('IBIAS1', np.int32), ('IBIAS0', np.int32), ('ENDPT2', '|S8'), ('ENDPT1', '|S8')])
+    mocked_com_headers = np.array([(99999,)], dtype=[('IBIAS2', '<i4')])
 
     mock_read_headers.return_value = mocked_ast_headers, mocked_com_headers
     dastcom5.read_record(740473)
     mock_open.assert_called_with(os.path.join(dastcom5.DBS_LOCAL_PATH, 'dast5_le.dat'), 'rb')
-    dastcom5.read_record(740473+1)
+    dastcom5.read_record(740473 + 1)
     mock_open.assert_called_with(os.path.join(dastcom5.DBS_LOCAL_PATH, 'dcom5_le.dat'), 'rb')
 
 
@@ -72,7 +72,7 @@ def test_read_record(mock_open, mock_np_fromfile, mock_read_headers):
 @mock.patch('poliastro.neos.dastcom5.os.path.isdir')
 @mock.patch('poliastro.neos.dastcom5.urllib.request')
 def test_download_dastcom5_raises_error_when_folder_exists(mock_request, mock_isdir, mock_zipfile, mock_makedirs):
-    mock_isdir.side_effect = lambda x:  x == os.path.join(dastcom5.POLIASTRO_LOCAL_PATH, 'dastcom5')
+    mock_isdir.side_effect = lambda x: x == os.path.join(dastcom5.POLIASTRO_LOCAL_PATH, 'dastcom5')
     with pytest.raises(FileExistsError):
         dastcom5.download_dastcom5()
     mock_isdir.assert_called_once_with(os.path.join(dastcom5.POLIASTRO_LOCAL_PATH, 'dastcom5'))
@@ -93,10 +93,9 @@ def test_download_dastcom5_creates_folder(mock_isdir, mock_zipfile, mock_makedir
 @mock.patch('poliastro.neos.dastcom5.os.path.isdir')
 @mock.patch('poliastro.neos.dastcom5.urllib.request.urlretrieve')
 def test_download_dastcom5_downloads_file(mock_request, mock_isdir, mock_zipfile):
-    mock_isdir.side_effect = lambda x:  x == dastcom5.POLIASTRO_LOCAL_PATH
+    mock_isdir.side_effect = lambda x: x == dastcom5.POLIASTRO_LOCAL_PATH
     mock_zipfile.is_zipfile.return_value = False
     dastcom5.download_dastcom5()
     mock_request.assert_called_once_with(dastcom5.FTP_DB_URL + 'dastcom5.zip',
                                          os.path.join(dastcom5.POLIASTRO_LOCAL_PATH, 'dastcom5.zip'),
                                          dastcom5._show_download_progress)
-
diff --git a/src/poliastro/neos/tests/test_neos_neows.py b/src/poliastro/neos/tests/test_neos_neows.py
index 5d818d2e..2786f5c6 100644
--- a/src/poliastro/neos/tests/test_neos_neows.py
+++ b/src/poliastro/neos/tests/test_neos_neows.py
@@ -39,7 +39,7 @@ def test_orbit_from_spk_id_raises_when_error(mock_get):
     resp = requests.Response()
 
     resp.status_code = 404
-    mock_get.return_value = resp   
+    mock_get.return_value = resp
     with pytest.raises(requests.HTTPError):
         ss = neows.orbit_from_spk_id('')
 
@@ -59,7 +59,7 @@ def test_spk_id_from_name_raises_when_error(mock_get):
 def test_spk_id_from_name_parses_body(mock_get, mock_response):
     with open('src/poliastro/tests/table.html', 'r') as demo_html:
         html = demo_html.read().replace('\n', '')
-    
+
     mock_response.text = html
     mock_get.return_value = mock_response
     assert '2000433' == neows.spk_id_from_name('')
diff --git a/src/poliastro/testing.py b/src/poliastro/testing.py
index 224395b2..7697264e 100644
--- a/src/poliastro/testing.py
+++ b/src/poliastro/testing.py
@@ -7,7 +7,7 @@ import pytest
 
 
 def test():
-    '''Initiate poliastro testing
-    
-    '''
+    """Initiate poliastro testing
+
+    """
     pytest.main([os.path.dirname(os.path.abspath(__file__))])
diff --git a/src/poliastro/tests/test_hyper.py b/src/poliastro/tests/test_hyper.py
index e652b3cf..aeaf6196 100644
--- a/src/poliastro/tests/test_hyper.py
+++ b/src/poliastro/tests/test_hyper.py
@@ -9,7 +9,7 @@ from poliastro.hyper import hyp2f1b as hyp2f1
 
 @pytest.mark.parametrize("x", np.linspace(0, 1, num=11))
 def test_hyp2f1_battin_scalar(x):
-    expected_res = special.hyp2f1(3, 1, 5/2, x)
+    expected_res = special.hyp2f1(3, 1, 5 / 2, x)
 
     res = hyp2f1(x)
     assert_allclose(res, expected_res)
diff --git a/src/poliastro/tests/test_plotting.py b/src/poliastro/tests/test_plotting.py
index fbbf308b..3b0fca52 100644
--- a/src/poliastro/tests/test_plotting.py
+++ b/src/poliastro/tests/test_plotting.py
@@ -3,8 +3,6 @@ import pytest
 
 import astropy.units as u
 
-import matplotlib
-matplotlib.use("Agg")  # use Agg backend for these tests
 import matplotlib.pyplot as plt
 
 from poliastro.examples import iss
@@ -12,7 +10,7 @@ from poliastro.examples import iss
 from poliastro.plotting import OrbitPlotter
 
 
-def test_OrbitPlotter_has_axes():
+def test_orbitplotter_has_axes():
     ax = "Unused axes"
     op = OrbitPlotter(ax)
     assert op.ax is ax
diff --git a/src/poliastro/twobody/tests/test_decorators.py b/src/poliastro/twobody/tests/test_decorators.py
new file mode 100644
index 00000000..6a24852f
--- /dev/null
+++ b/src/poliastro/twobody/tests/test_decorators.py
@@ -0,0 +1,31 @@
+import inspect
+
+from astropy import units as u
+
+from poliastro.twobody.decorators import state_from_vector
+
+
+def fun(t, ss):
+    return 1
+
+
+def decorated_fun(t, u_, k):
+    pass
+
+
+def test_decorator_has_correct_signature():
+    expected_signature = inspect.getfullargspec(decorated_fun)
+
+    new_fun = state_from_vector(fun)
+
+    assert inspect.getfullargspec(new_fun).args == expected_signature.args
+
+
+def test_decorated_function_calls_rvstate():
+    _t = 0
+    _u = [1, 0, 0, 0, 1, 0]
+    _k = 1
+
+    new_fun = state_from_vector(fun)
+
+    assert new_fun(_t, _u, _k) == 1
diff --git a/src/poliastro/twobody/tests/test_propagation.py b/src/poliastro/twobody/tests/test_propagation.py
index e44a7ac4..68c64d34 100644
--- a/src/poliastro/twobody/tests/test_propagation.py
+++ b/src/poliastro/twobody/tests/test_propagation.py
@@ -69,9 +69,9 @@ def test_apply_zero_maneuver_returns_equal_state():
     dv = [0, 0, 0] * u.km / u.s
     orbit_new = ss.apply_maneuver([(dt, dv)])
     assert_allclose(orbit_new.r.to(u.km).value,
-                        ss.r.to(u.km).value)
+                    ss.r.to(u.km).value)
     assert_allclose(orbit_new.v.to(u.km / u.s).value,
-                        ss.v.to(u.km / u.s).value)
+                    ss.v.to(u.km / u.s).value)
 
 
 def test_cowell_propagation_callback():
@@ -134,9 +134,8 @@ def test_cowell_propagation_circle_to_circle():
                   tof.to(u.s).value,
                   ad=constant_accel)
 
-    ss_final = Orbit.from_vectors(Earth,
-                       r * u.km,
-                       v * u.km / u.s)
+    ss_final = Orbit.from_vectors(
+        Earth, r * u.km, v * u.km / u.s)
 
     da_a0 = (ss_final.a - ss.a) / ss.a
     dv_v0 = abs(norm(ss_final.v) - norm(ss.v)) / norm(ss.v)
diff --git a/src/poliastro/twobody/tests/test_states.py b/src/poliastro/twobody/tests/test_states.py
index 29e07e93..82a519da 100644
--- a/src/poliastro/twobody/tests/test_states.py
+++ b/src/poliastro/twobody/tests/test_states.py
@@ -63,9 +63,9 @@ def test_perigee_and_apogee():
     _a = 1.0 * u.deg  # Unused angle
     ss = ClassicalState(Earth, a, ecc, _a, _a, _a, _a)
     assert_allclose(ss.r_a.to(u.km).value,
-                        expected_r_a.to(u.km).value)
+                    expected_r_a.to(u.km).value)
     assert_allclose(ss.r_p.to(u.km).value,
-                        expected_r_p.to(u.km).value)
+                    expected_r_p.to(u.km).value)
 
 
 def test_convert_from_rv_to_coe():
@@ -138,4 +138,4 @@ def test_pqw_returns_dimensionless():
 
     assert p.unit == u.one
     assert q.unit == u.one
-    assert w.unit == u.one
\ No newline at end of file
+    assert w.unit == u.one