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

Issue:
Invalid version schema "0.0.0.dev+.gXXX" - may not work with newer versions of setuptools, pip, and PyPI
```C:\Miniconda-x64\envs\test\lib\site-packages\setuptools\dist.py:350: UserWarning: The version specified (u'0.0.0.dev+.g5c641b2cb1') is an invalid version, this may not work as expected with newer versions of setuptools, pip, and PyPI. Please see PEP 440 for more details.```

see also https://ci.appveyor.com/project/obspy/obspy/build/1.0.5140-warnings/job/rxhusf826vk5atia




Discussion:
I don't know where this version number is coming from.. likely a downloaded dev tarball from github, in any case this should fix it I think:

```diff
--- a/obspy/core/util/version.py
+++ b/obspy/core/util/version.py
@@ -107,7 +107,7 @@ def call_git_describe(abbrev=10, dirty=True,
     # (this line prevents official releases)
     # should work again now, see #482 and obspy/obspy@b437f31
     if "-" not in line and "." not in line:
-        version = "0.0.0.dev+.g%s" % line
+        version = "0.0.0.dev+0.g%s" % line
     else:
         parts = line.split('-', 1)
         version = parts[0]
```
> I don't know where this version number is coming from.. likely a downloaded dev tarball from github, in any case this should fix it I think:

it was an automated CI build after commit https://github.com/obspy/obspy/commit/5c641b2cb11b1b6882eed27c01d40747828cc373 within https://github.com/obspy/obspy/pull/1888




Resolving patch:
diff --git a/obspy/core/util/version.py b/obspy/core/util/version.py
--- a/obspy/core/util/version.py
+++ b/obspy/core/util/version.py
@@ -107,7 +107,7 @@ def call_git_describe(abbrev=10, dirty=True,
     # (this line prevents official releases)
     # should work again now, see #482 and obspy/obspy@b437f31
     if "-" not in line and "." not in line:
-        version = "0.0.0.dev+.g%s" % line
+        version = "0.0.0.dev+0.g%s" % line
     else:
         parts = line.split('-', 1)
         version = parts[0]


Test patch:
Repository: obspy/obspy

Issue:
strange output using beachballs
Hi,
I m experiencing a strange behaviour of Obspy when plotting beachballs.
Try the following example:
mt0 gives me the expected beach ball associated with strike/dip/rake = 236/68/177.
mt1 is identical with mt0 but the decimals are latter troncated and gives a blue beach ball (?!?).
Any idea why?

Thanks in advance,

Thomas

```python
from obspy.imaging.mopad_wrapper import beachball
mt0 = [6.07624723e+19,         -6.34127963e+19,        2.65032397e+18,         2.65142658e+19,         -1.75252565e+19,         -2.10743112e+19]
mt1 = [6.0762472348250243e+19, -6.341279632309221e+19, 2.6503239748419671e+18, 2.6514265814783787e+19, -1.7525256484369068e+19, -2.1074311214873211e+19]
beachball(mt0, size=40, linewidth=2, facecolor='b',mopad_basis = 'NED', xy=(-70, 80))
beachball(mt1, size=40, linewidth=2, facecolor='b',mopad_basis = 'NED', xy=(-70, 80))
```

my obspy: 1.1.0-py27_1 conda-forge


Discussion:
I can reproduce this. We've had some numerical stability issues with very large numbers before - maybe we should start to internally normalize moment tensor before we plot them?

You can work-around this particular issue by normalizing the tensor components. This should result in more stable plots.

```
mt1 = np.array(mt1)
mt1 /= np.linalg.norm(mt1)
```

Then the plots work for both cases.
Ok, 

Thank-you Lion for the support and for this quick workaround.

Thomas.
> I can reproduce this. We've had some numerical stability issues with very large numbers before - maybe we should start to internally normalize moment tensor before we plot them?

sounds like a simple workaround and easy fix  :+1: 

Resolving patch:
diff --git a/obspy/imaging/mopad_wrapper.py b/obspy/imaging/mopad_wrapper.py
--- a/obspy/imaging/mopad_wrapper.py
+++ b/obspy/imaging/mopad_wrapper.py
@@ -52,6 +52,19 @@
 }
 
 
+def _normalize_focmec(fm):
+    """
+    Improve stability of plots by normalizing the moment tensors. The scale
+    does not matter for the beachballs.
+    """
+    # Only normalize 6 component tensors.
+    if len(fm) != 6:
+        return fm
+    fm = np.array(fm, dtype=np.float64)
+    fm /= np.linalg.norm(fm)
+    return fm
+
+
 def beach(fm, linewidth=2, facecolor='b', bgcolor='w', edgecolor='k',
           alpha=1.0, xy=(0, 0), width=200, size=100, nofill=False,
           zorder=100, mopad_basis='USE', axes=None):
@@ -113,6 +126,7 @@ def beach(fm, linewidth=2, facecolor='b', bgcolor='w', edgecolor='k',
     ``'NWU'`` North, West, Up     Stein and Wysession 2003
     ========= =================== =============================================
     """
+    fm = _normalize_focmec(fm)
     # initialize beachball
     mt = mopad_MomentTensor(fm, system=mopad_basis)
     bb = mopad_BeachBall(mt, npoints=size)
@@ -279,6 +293,7 @@ def beachball(fm, linewidth=2, facecolor='b', bgcolor='w', edgecolor='k',
             mt = [1, 2, 3, -4, -5, -10]
             beachball(mt, mopad_basis='NED')
     """
+    fm = _normalize_focmec(fm)
     mopad_kwargs = {}
     loc = locals()
     # map to kwargs used in mopad


Test patch:
Repository: obspy/obspy

Issue:
FDSN routing client has a locale dependency
There's a dummy call to `time.strptime` in the module init that uses locale-specific formatting, which fails under locales that don't use the same names (ie. "Nov" for the 11th month of the year).

```
>>> import locale
>>> locale.setlocale(locale.LC_TIME, ('zh_CN', 'UTF-8'))
'zh_CN.UTF-8'
>>> from obspy.clients.fdsn.routing.routing_client import RoutingClient
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/workspace/anaconda/envs/django/lib/python2.7/site-packages/obspy/clients/fdsn/__init__.py", line 242, in <module>
    from .routing.routing_client import RoutingClient  # NOQA
  File "/workspace/anaconda/envs/django/lib/python2.7/site-packages/obspy/clients/fdsn/routing/__init__.py", line 25, in <module>
    time.strptime("30 Nov 00", "%d %b %y")
  File "/workspace/anaconda/envs/django/lib/python2.7/_strptime.py", line 478, in _strptime_time
    return _strptime(data_string, format)[0]
  File "/workspace/anaconda/envs/django/lib/python2.7/_strptime.py", line 332, in _strptime
    (data_string, format))
ValueError: time data u'30 Nov 00' does not match format u'%d %b %y'
```

I believe switching this to an ISO8601-like string would be locale-agnostic:

    time.strptime("2000/11/30", "%Y/%m/%d")



Discussion:

Resolving patch:
diff --git a/obspy/clients/fdsn/routing/__init__.py b/obspy/clients/fdsn/routing/__init__.py
--- a/obspy/clients/fdsn/routing/__init__.py
+++ b/obspy/clients/fdsn/routing/__init__.py
@@ -22,7 +22,7 @@
 #
 # See https://bugs.python.org/issue7980
 import time
-time.strptime("30 Nov 00", "%d %b %y")
+time.strptime("2000/11/30", "%Y/%m/%d")
 
 
 if __name__ == '__main__':  # pragma: no cover


Test patch:
Repository: obspy/obspy

Issue:
miniseed to ascii conversion via stream.write not writing some time stamps correctly
It looks like some time values are not being written correctly using `stream.write(format='TSPAIR')` where the stream was `read(<miniseed file>)`.  I've attached a miniseed file, the ascii output from `stream.write`, and also the ascii output from `mseed2ascii`, (https://github.com/iris-edu/mseed2ascii).
I was expecting these outputs to match.


Before submitting an Issue, please review the [Issue Guidelines](https://github.com/obspy/obspy/blob/master/CONTRIBUTING.md#submitting-an-issue).

* Please check whether the bug was already reported or fixed.
* Please provide the following information:
  -  ObsPy version, Python version and Platform (Windows, OSX, Linux ...)
1.1.0, OSX, Python 3.6.4

  -  How did you install ObsPy and Python (pip, anaconda, from source ...)
anaconda

  -  If possible please supply a short, self contained, correct example that
     demonstrates the issue.

```python
st = read('compress_0528_0100_CIAGO__HNZ___2016172.ms')
st.write('compress_0528_0100_CIAGO__HNZ___2016172.txt', format='TSPAIR')
```

  -  If this is a regression (used to work in an earlier version of ObsPy),
     please note when it used to work.

[obspy_submit.tar.gz](https://github.com/obspy/obspy/files/2051436/obspy_submit.tar.gz)




Discussion:
Can confirm this is a bug.

Resolving patch:
diff --git a/obspy/io/ascii/core.py b/obspy/io/ascii/core.py
--- a/obspy/io/ascii/core.py
+++ b/obspy/io/ascii/core.py
@@ -462,11 +462,9 @@ def _write_tspair(stream, filename, custom_fmt=None,
             header = _format_header(stats, 'TSPAIR', dataquality, dtype, unit)
             fh.write(header.encode('ascii', 'strict'))
             # write data
-            times = np.linspace(stats.starttime.timestamp,
-                                stats.endtime.timestamp, stats.npts)
-            for t, d in zip(times, trace.data):
+            for t, d in zip(trace.times(type='utcdatetime'), trace.data):
                 # .26s cuts the Z from the time string
-                line = ('%.26s  ' + fmt + '\n') % (UTCDateTime(t), d)
+                line = ('%.26s  ' + fmt + '\n') % (t, d)
                 fh.write(line.encode('ascii', 'strict'))
 
 


Test patch:
Repository: obspy/obspy

Issue:
Check if taupy model is already opened 
As a suggestion, I think it makes more sense to check if `model` is already an instance of `TauPyModel` here:
https://github.com/obspy/obspy/blob/6b43db0b6fec380e4d93d8d468eececcb74b4781/obspy/taup/tau.py#L942





Discussion:

Resolving patch:
diff --git a/obspy/taup/tau.py b/obspy/taup/tau.py
--- a/obspy/taup/tau.py
+++ b/obspy/taup/tau.py
@@ -939,7 +939,8 @@ def plot_travel_times(source_depth, phase_list=("ttbasic",), min_degrees=0,
     import matplotlib.pyplot as plt
 
     # compute the requested arrivals:
-    model = TauPyModel(model)
+    if not isinstance(model, TauPyModel):
+        model = TauPyModel(model)
 
     # a list of epicentral distances without a travel time, and a flag:
     notimes = []


Test patch:
Repository: obspy/obspy

Issue:
ZeroDivisionError reading sc3ml with zero sampling rate
obspy read_inventory does not handle the case when reading sc3ml that the sampleRateNumerator and sampleRateDenominator are *both* zero. This occasionally crops up in real world data, in which case the channel sample_rate should be set to zero.

Add a test case to elicit the ZeroDivisionError and fix in sc3ml inventory reader.

Context:
* Obspy version: 1.1.1rc1
* Python version: all
* Platforms: all




Discussion:

Resolving patch:
diff --git a/obspy/io/seiscomp/inventory.py b/obspy/io/seiscomp/inventory.py
--- a/obspy/io/seiscomp/inventory.py
+++ b/obspy/io/seiscomp/inventory.py
@@ -407,7 +407,10 @@ def _read_channel(inventory_root, cha_element, _ns):
     numerator = _tag2obj(cha_element, _ns("sampleRateNumerator"), int)
     denominator = _tag2obj(cha_element, _ns("sampleRateDenominator"), int)
 
-    rate = numerator / denominator
+    # If numerator is zero, set rate to zero irrespective of the denominator.
+    # If numerator is non-zero and denominator zero, will raise
+    # ZeroDivisionError.
+    rate = numerator / denominator if numerator != 0 else 0
 
     channel.sample_rate_ratio_number_samples = numerator
     channel.sample_rate_ratio_number_seconds = denominator


Test patch:
diff --git a/obspy/io/seiscomp/tests/data/channel_level.sc3ml b/obspy/io/seiscomp/tests/data/channel_level.sc3ml
--- a/obspy/io/seiscomp/tests/data/channel_level.sc3ml
+++ b/obspy/io/seiscomp/tests/data/channel_level.sc3ml
@@ -386,8 +386,8 @@
             <dataloggerChannel>0</dataloggerChannel>
             <sensorSerialNumber>yyyy</sensorSerialNumber>
             <sensorChannel>0</sensorChannel>
-            <sampleRateNumerator>40</sampleRateNumerator>
-            <sampleRateDenominator>1</sampleRateDenominator>
+            <sampleRateNumerator>0</sampleRateNumerator>
+            <sampleRateDenominator>0</sampleRateDenominator>
             <depth>4</depth>
             <azimuth>0</azimuth>
             <dip>-90</dip>
Repository: obspy/obspy

Issue:
incorrect bitmask in libgcf
libgcf.py, line 107, masks the bottom 4 bits for the compresssion code:
`    compression = compress & 0b00001111  # get compression code`

This should mask off only the bottom 3 bits (the 4th is allocated for something else in the near future):
`    compression = compress & 0b00000111  # get compression code`

incorrect bitmask in libgcf
libgcf.py, line 107, masks the bottom 4 bits for the compresssion code:
`    compression = compress & 0b00001111  # get compression code`

This should mask off only the bottom 3 bits (the 4th is allocated for something else in the near future):
`    compression = compress & 0b00000111  # get compression code`



Discussion:
That's an easy fix.. 
That's an easy fix.. 

Resolving patch:
diff --git a/obspy/io/gcf/libgcf.py b/obspy/io/gcf/libgcf.py
--- a/obspy/io/gcf/libgcf.py
+++ b/obspy/io/gcf/libgcf.py
@@ -104,7 +104,7 @@ def read_data_block(f, headonly=False, channel_prefix="HH", **kwargs):
     # number of 32bit records (num_records)
     reserved, sps, compress, num_records = np.frombuffer(f.read(4), count=4,
                                                          dtype='>u1')
-    compression = compress & 0b00001111  # get compression code
+    compression = compress & 0b00000111  # get compression code
     t_offset = compress >> 4  # get time offset
     if t_offset > 0:
         starttime = starttime + t_offset / TIME_OFFSETS_D[sps]


Test patch:
Repository: obspy/obspy

Issue:
libgcf assumes 6 character stream IDs
The GCF reader (libgcf.py) assumes the stream ID is 6 characters, and hard-codes access to element 4:
```
    header['station'] = stid[:4]
    header['channel'] = (channel_prefix[:2] + stid[4]).upper()
```

The Stream ID is made up of three parts: first is up to 4 characters for serial number, then one char for channel letter, then one channel for tap number. Rather than assume the channel letter is at index 4, it should be considered the 2nd last...
```
    header['station'] = stid[:-2]
    header['channel'] = (channel_prefix[:2] + stid[-2]).upper()
```

This was causing some valid GCF files to be considered invalid when they had short IDs.
libgcf assumes 6 character stream IDs
The GCF reader (libgcf.py) assumes the stream ID is 6 characters, and hard-codes access to element 4:
```
    header['station'] = stid[:4]
    header['channel'] = (channel_prefix[:2] + stid[4]).upper()
```

The Stream ID is made up of three parts: first is up to 4 characters for serial number, then one char for channel letter, then one channel for tap number. Rather than assume the channel letter is at index 4, it should be considered the 2nd last...
```
    header['station'] = stid[:-2]
    header['channel'] = (channel_prefix[:2] + stid[-2]).upper()
```

This was causing some valid GCF files to be considered invalid when they had short IDs.


Discussion:
Easy to fix, thanks for the patch.
Easy to fix, thanks for the patch.

Resolving patch:
diff --git a/obspy/io/gcf/libgcf.py b/obspy/io/gcf/libgcf.py
--- a/obspy/io/gcf/libgcf.py
+++ b/obspy/io/gcf/libgcf.py
@@ -119,8 +119,8 @@ def read_data_block(f, headonly=False, channel_prefix="HH", **kwargs):
     npts = num_records * compression  # number of samples
     header = {}
     header['starttime'] = starttime
-    header['station'] = stid[:4]
-    header['channel'] = (channel_prefix[:2] + stid[4]).upper()
+    header['station'] = stid[:-2]
+    header['channel'] = (channel_prefix[:2] + stid[-2]).upper()
     header['sampling_rate'] = float(sps)
     header['npts'] = npts
     if headonly:


Test patch:
Repository: obspy/obspy

Issue:
obspy-scan doc does not document depiction of data overlaps
Hi - Just a short suggestion to modify a sentence in the obspy-scan documentation reporting that blue lines/blocks represent data overlaps.  (I had to read the code to figure this out.)  Suggest

"Start times of traces with available data are marked by crosses, gaps are indicated by vertical red lines."

be changed to read,

"Start times of traces with available data are marked by crosses, gaps are indicated by vertical red lines, and overlaps are indicated by blue lines."

George Helffrich




Discussion:
Thanks for reporting!
To be fixed here: https://github.com/obspy/obspy/blob/84fd243dcdecdc7b55f652929a4870ff47bc8e2c/obspy/imaging/scripts/scan.py#L9

Resolving patch:
diff --git a/obspy/imaging/scripts/scan.py b/obspy/imaging/scripts/scan.py
--- a/obspy/imaging/scripts/scan.py
+++ b/obspy/imaging/scripts/scan.py
@@ -6,7 +6,7 @@
 Scan all specified files/directories, determine which time spans are covered
 for which stations and plot everything in summarized in one overview plot.
 Start times of traces with available data are marked by crosses, gaps are
-indicated by vertical red lines.
+indicated by vertical red lines, and overlaps are indicated by blue lines.
 The sampling rate must stay the same for each station, but may vary between the
 stations.
 


Test patch:
Repository: obspy/obspy

Issue:
Remove annoying print statement
https://github.com/obspy/obspy/blob/b99a2d6d0443e517e6b46f37cb751f4c1c184390/obspy/signal/spectral_estimation.py#L838

It's probably a leftover (looks like the typical print statement issued for some kind of debug, and as often happens, there forgotten). You will excuse me if I left it to be done (I do not issue a PR for such a small detail), I hope you can remove it in future releases, it's obviously really annoying when calling that function in a process handling stderr stdout and warnings captured.

Thanks
R
Remove annoying print statement
https://github.com/obspy/obspy/blob/b99a2d6d0443e517e6b46f37cb751f4c1c184390/obspy/signal/spectral_estimation.py#L838

It's probably a leftover (looks like the typical print statement issued for some kind of debug, and as often happens, there forgotten). You will excuse me if I left it to be done (I do not issue a PR for such a small detail), I hope you can remove it in future releases, it's obviously really annoying when calling that function in a process handling stderr stdout and warnings captured.

Thanks
R


Discussion:

Resolving patch:
diff --git a/obspy/signal/spectral_estimation.py b/obspy/signal/spectral_estimation.py
--- a/obspy/signal/spectral_estimation.py
+++ b/obspy/signal/spectral_estimation.py
@@ -833,9 +833,9 @@ def __process(self, tr):
             tr.data = tr.data[:-1]
         # one last check..
         if len(tr) != self.len:
-            msg = "Got a piece of data with wrong length. Skipping"
+            msg = ("Got a piece of data with wrong length. Skipping:\n" +
+                   str(tr))
             warnings.warn(msg)
-            print(len(tr), self.len)
             return False
         # being paranoid, only necessary if in-place operations would follow
         tr.data = tr.data.astype(np.float64)


Test patch:
Repository: obspy/obspy

Issue:
Error 429 for INGV FDSN dataselect webservices 
We have noticed that there is a recurrent error when requesting data using  the INGV dataselect web services and choosing all stations within a box it can occur that some stations do not have data so that multiple requests pile up and as a result the 429 error appears (https://httpstatuses.com/429). 

429 TOO MANY REQUESTS
The user has sent too many requests in a given amount of time ("rate limiting").

In essence, there is a simple fix that should be inserted into the function "raise_on_error(code, data)" in the "client" class.

i.e., something like
#### Too many request.
    elif code == 429:
        msg = (“Sent too many requests in a given amount of time ("rate limiting") "
               “Wait before making a new request.", server_info)
        raise NotImplementedError(msg)




Discussion:

Resolving patch:
diff --git a/obspy/clients/fdsn/client.py b/obspy/clients/fdsn/client.py
--- a/obspy/clients/fdsn/client.py
+++ b/obspy/clients/fdsn/client.py
@@ -1737,6 +1737,10 @@ def raise_on_error(code, data):
         msg = ("The request URI is too large. Please contact the ObsPy "
                "developers.", server_info)
         raise NotImplementedError(msg)
+    elif code == 429:
+        msg = ("Sent too many requests in a given amount of time ('rate "
+               "limiting'). Wait before making a new request.", server_info)
+        raise FDSNException(msg, server_info)
     elif code == 500:
         raise FDSNException("Service responds: Internal server error",
                             server_info)


Test patch:
Repository: obspy/obspy

Issue:
FutureWarning for signal.headers
This happens with current master and NumPy 1.17.3:
```python
>>> from obspy import signal
```
```
[...]/obspy/signal/headers.py:93: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  ], align=True)
```
FutureWarning for signal.headers
This happens with current master and NumPy 1.17.3:
```python
>>> from obspy import signal
```
```
[...]/obspy/signal/headers.py:93: FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecated; in a future version of numpy, it will be understood as (type, (1,)) / '(1,)type'.
  ], align=True)
```


Discussion:

Resolving patch:
diff --git a/obspy/signal/headers.py b/obspy/signal/headers.py
--- a/obspy/signal/headers.py
+++ b/obspy/signal/headers.py
@@ -87,9 +87,9 @@
 clibsignal.utl_geo_km.restype = C.c_void_p
 
 head_stalta_t = np.dtype([
-    (native_str('N'), np.uint32, 1),
-    (native_str('nsta'), np.uint32, 1),
-    (native_str('nlta'), np.uint32, 1),
+    (native_str('N'), np.uint32),
+    (native_str('nsta'), np.uint32),
+    (native_str('nlta'), np.uint32),
 ], align=True)
 
 clibsignal.stalta.argtypes = [


Test patch:
Repository: obspy/obspy

Issue:
Check Python 3.8 compatibility
Python 3.8.0rc1 is out, we should check if there are any issues with our master branch before releasing 1.2.0.

Hopefull there will be packages on conda-forge, then we could simply add it to the CI build matrix. See conda-forge/python-feedstock#270


Discussion:
It's available in Fedora Rawhide, if you want to grab a container and try it out.
Python 3.8 officially out. Seems like it's not yet on Anaconda but should be soon, I expect.
New PR to follow: https://github.com/conda-forge/python-feedstock/pull/274

Resolving patch:
diff --git a/obspy/io/gcf/libgcf.py b/obspy/io/gcf/libgcf.py
--- a/obspy/io/gcf/libgcf.py
+++ b/obspy/io/gcf/libgcf.py
@@ -52,9 +52,10 @@ def decode36(data):
     while data:
         imed = data % 36
         if imed > 9:
-            c = chr(imed - 10 + ord('A'))
+            pos = imed - 10 + ord('A')
         else:
-            c = chr(imed + ord('0'))
+            pos = imed + ord('0')
+        c = chr(pos)
         s = c + s
         data = data // 36
     return s
@@ -92,9 +93,17 @@ def read_data_block(f, headonly=False, channel_prefix="HH", **kwargs):
     sysid = np.frombuffer(sysid, count=1, dtype='>u4')
     if sysid >> 31 & 0b1 > 0:
         sysid = (sysid << 6) >> 6
+    if isinstance(sysid, np.ndarray) and sysid.shape == (1,):
+        sysid = sysid[0]
+    else:
+        raise ValueError('sysid should be a single element np.ndarray')
     sysid = decode36(sysid)
     # get Stream ID
     stid = np.frombuffer(f.read(4), count=1, dtype='>u4')
+    if isinstance(stid, np.ndarray) and stid.shape == (1,):
+        stid = stid[0]
+    else:
+        raise ValueError('stid should be a single element np.ndarray')
     stid = decode36(stid)
     # get Date & Time
     data = np.frombuffer(f.read(4), count=1, dtype='>u4')
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -801,6 +801,7 @@ def setupPackage():
             'Programming Language :: Python :: 3.5',
             'Programming Language :: Python :: 3.6',
             'Programming Language :: Python :: 3.7',
+            'Programming Language :: Python :: 3.8',
             'Topic :: Scientific/Engineering',
             'Topic :: Scientific/Engineering :: Physics'],
         keywords=KEYWORDS,


Test patch: