We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
ScanProsite no longer working
### Setup

I am reporting a problem with Biopython version, Python version, and operating
system as follows:

```python
import sys; print(sys.version)
import platform; print(platform.python_implementation()); print(platform.platform())
import Bio; print(Bio.__version__)
```

(*Please copy and run the above in your Python, and copy-and-paste the output*)
3.7.16 (default, Mar 10 2023, 03:25:26) 
[GCC 7.3.1 20180712 (Red Hat 7.3.1-15)]
CPython
Linux-5.10.177-158.645.amzn2.x86_64-x86_64-with-glibc2.2.5
1.81

### Expected behaviour

Hi, I have been recently having difficulty with the ScanProsite module.  I am wondering if the ScanProsite mirror needs to updated.  Thank you in advance.

### Actual behaviour

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.7/urllib/request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib64/python3.7/urllib/request.py", line 531, in open
    response = meth(req, response)
  File "/usr/lib64/python3.7/urllib/request.py", line 641, in http_response
    'http', request, response, code, msg, hdrs)
  File "/usr/lib64/python3.7/urllib/request.py", line 569, in error
    return self._call_chain(*args)
  File "/usr/lib64/python3.7/urllib/request.py", line 503, in _call_chain
    result = func(*args)
  File "/usr/lib64/python3.7/urllib/request.py", line 649, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 308: Permanent Redirect

### Steps to reproduce

    from Bio import ExPASy
    from Bio.ExPASy import ScanProsite
    import pandas as pd
    # Run input sequence through ScanProsite
    handle = ScanProsite.scan(sig = sequence, output = 'xml', lineage='Homo sapiens', mirror = 'https://prosite.expasy.org/cgi-bin/prosite/scanprosite/PSScan.cgi')
    result = ScanProsite.read(handle)
    df = pd.DataFrame(handle)


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `Bio/ExPASy/ScanProsite.py`
Content:
```
1 # Copyright 2009 by Michiel de Hoon. All rights reserved.
2 # This code is part of the Biopython distribution and governed by its
3 # license. Please see the LICENSE file that should have been included
4 # as part of this package.
5 
6 """Code for calling and parsing ScanProsite from ExPASy."""
7 
8 # Importing these functions with leading underscore as not intended for reuse
9 from urllib.request import urlopen
10 from urllib.parse import urlencode
11 
12 from xml.sax import handler
13 from xml.sax.expatreader import ExpatParser
14 
15 
16 class Record(list):
17     """Represents search results returned by ScanProsite.
18 
19     This record is a list containing the search results returned by
20     ScanProsite. The record also contains the data members n_match,
21     n_seq, capped, and warning.
22     """
23 
24     def __init__(self):
25         """Initialize the class."""
26         self.n_match = None
27         self.n_seq = None
28         self.capped = None
29         self.warning = None
30 
31 
32 # October 28th 2020 it was recognised that between October 10th 2020 and October
33 # 28th the main url of prosite changed from https://www.expasy.org to
34 # https://prosite.expasy.org. Thus a change in the mirror was issued from
35 # https://www.expasy.org to https://prosite.expasy.org.
36 def scan(seq="", mirror="https://prosite.expasy.org", output="xml", **keywords):
37     """Execute a ScanProsite search.
38 
39     Arguments:
40      - mirror:   The ScanProsite mirror to be used
41                  (default: https://prosite.expasy.org).
42      - seq:      The query sequence, or UniProtKB (Swiss-Prot,
43                  TrEMBL) accession
44      - output:   Format of the search results
45                  (default: xml)
46 
47     Further search parameters can be passed as keywords; see the
48     documentation for programmatic access to ScanProsite at
49     https://prosite.expasy.org/scanprosite/scanprosite_doc.html
50     for a description of such parameters.
51 
52     This function returns a handle to the search results returned by
53     ScanProsite. Search results in the XML format can be parsed into a
54     Python object, by using the Bio.ExPASy.ScanProsite.read function.
55 
56     """
57     parameters = {"seq": seq, "output": output}
58     for key, value in keywords.items():
59         if value is not None:
60             parameters[key] = value
61     command = urlencode(parameters)
62     url = f"{mirror}/cgi-bin/prosite/PSScan.cgi?{command}"
63     handle = urlopen(url)
64     return handle
65 
66 
67 def read(handle):
68     """Parse search results returned by ScanProsite into a Python object."""
69     content_handler = ContentHandler()
70     saxparser = Parser()
71     saxparser.setContentHandler(content_handler)
72     saxparser.parse(handle)
73     record = content_handler.record
74     return record
75 
76 
77 # The classes below are considered private
78 
79 
80 class Parser(ExpatParser):
81     """Process the result from a ScanProsite search (PRIVATE)."""
82 
83     def __init__(self):
84         """Initialize the class."""
85         ExpatParser.__init__(self)
86         self.firsttime = True
87 
88     def feed(self, data, isFinal=0):
89         """Raise an Error if plain text is received in the data.
90 
91         This is to show the Error messages returned by ScanProsite.
92         """
93         # Error messages returned by the ScanProsite server are formatted as
94         # as plain text instead of an XML document. To catch such error
95         # messages, we override the feed method of the Expat parser.
96         # The error message is (hopefully) contained in the data that was just
97         # fed to the parser.
98         if self.firsttime:
99             if data[:5].decode("utf-8") != "<?xml":
100                 raise ValueError(data)
101         self.firsttime = False
102         return ExpatParser.feed(self, data, isFinal)
103 
104 
105 class ContentHandler(handler.ContentHandler):
106     """Process and fill in the records, results of the search (PRIVATE)."""
107 
108     integers = ("start", "stop")
109     strings = (
110         "sequence_ac",
111         "sequence_id",
112         "sequence_db",
113         "signature_ac",
114         "level",
115         "level_tag",
116     )
117 
118     def __init__(self):
119         """Initialize the class."""
120         self.element = []
121 
122     def startElement(self, name, attrs):
123         """Define the beginning of a record and stores the search record."""
124         self.element.append(name)
125         self.content = ""
126         if self.element == ["matchset"]:
127             self.record = Record()
128             self.record.n_match = int(attrs["n_match"])
129             self.record.n_seq = int(attrs["n_seq"])
130         elif self.element == ["matchset", "match"]:
131             match = {}
132             self.record.append(match)
133 
134     def endElement(self, name):
135         """Define the end of the search record."""
136         assert name == self.element.pop()
137         if self.element == ["matchset", "match"]:
138             match = self.record[-1]
139             if name in ContentHandler.integers:
140                 match[name] = int(self.content)
141             elif name in ContentHandler.strings:
142                 match[name] = self.content
143             else:
144                 # Unknown type, treat it as a string
145                 match[name] = self.content
146 
147     def characters(self, content):
148         """Store the record content."""
149         self.content += content
150
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/Bio/ExPASy/ScanProsite.py b/Bio/ExPASy/ScanProsite.py
--- a/Bio/ExPASy/ScanProsite.py
+++ b/Bio/ExPASy/ScanProsite.py
@@ -59,7 +59,7 @@
         if value is not None:
             parameters[key] = value
     command = urlencode(parameters)
-    url = f"{mirror}/cgi-bin/prosite/PSScan.cgi?{command}"
+    url = f"{mirror}/cgi-bin/prosite/scanprosite/PSScan.cgi?{command}"
     handle = urlopen(url)
     return handle

{"golden_diff": "diff --git a/Bio/ExPASy/ScanProsite.py b/Bio/ExPASy/ScanProsite.py\n--- a/Bio/ExPASy/ScanProsite.py\n+++ b/Bio/ExPASy/ScanProsite.py\n@@ -59,7 +59,7 @@\n         if value is not None:\n             parameters[key] = value\n     command = urlencode(parameters)\n-    url = f\"{mirror}/cgi-bin/prosite/PSScan.cgi?{command}\"\n+    url = f\"{mirror}/cgi-bin/prosite/scanprosite/PSScan.cgi?{command}\"\n     handle = urlopen(url)\n     return handle\n", "issue": "ScanProsite no longer working\n### Setup\r\n\r\nI am reporting a problem with Biopython version, Python version, and operating\r\nsystem as follows:\r\n\r\n```python\r\nimport sys; print(sys.version)\r\nimport platform; print(platform.python_implementation()); print(platform.platform())\r\nimport Bio; print(Bio.__version__)\r\n```\r\n\r\n(*Please copy and run the above in your Python, and copy-and-paste the output*)\r\n3.7.16 (default, Mar 10 2023, 03:25:26) \r\n[GCC 7.3.1 20180712 (Red Hat 7.3.1-15)]\r\nCPython\r\nLinux-5.10.177-158.645.amzn2.x86_64-x86_64-with-glibc2.2.5\r\n1.81\r\n\r\n### Expected behaviour\r\n\r\nHi, I have been recently having difficulty with the ScanProsite module.  I am wondering if the ScanProsite mirror needs to updated.  Thank you in advance.\r\n\r\n### Actual behaviour\r\n\r\nTraceback (most recent call last):\r\n  File \"<stdin>\", line 1, in <module>\r\n  File \"/usr/lib64/python3.7/urllib/request.py\", line 222, in urlopen\r\n    return opener.open(url, data, timeout)\r\n  File \"/usr/lib64/python3.7/urllib/request.py\", line 531, in open\r\n    response = meth(req, response)\r\n  File \"/usr/lib64/python3.7/urllib/request.py\", line 641, in http_response\r\n    'http', request, response, code, msg, hdrs)\r\n  File \"/usr/lib64/python3.7/urllib/request.py\", line 569, in error\r\n    return self._call_chain(*args)\r\n  File \"/usr/lib64/python3.7/urllib/request.py\", line 503, in _call_chain\r\n    result = func(*args)\r\n  File \"/usr/lib64/python3.7/urllib/request.py\", line 649, in http_error_default\r\n    raise HTTPError(req.full_url, code, msg, hdrs, fp)\r\nurllib.error.HTTPError: HTTP Error 308: Permanent Redirect\r\n\r\n### Steps to reproduce\r\n\r\n    from Bio import ExPASy\r\n    from Bio.ExPASy import ScanProsite\r\n    import pandas as pd\r\n    # Run input sequence through ScanProsite\r\n    handle = ScanProsite.scan(sig = sequence, output = 'xml', lineage='Homo sapiens', mirror = 'https://prosite.expasy.org/cgi-bin/prosite/scanprosite/PSScan.cgi')\r\n    result = ScanProsite.read(handle)\r\n    df = pd.DataFrame(handle)\r\n\n", "before_files": [{"content": "# Copyright 2009 by Michiel de Hoon. All rights reserved.\n# This code is part of the Biopython distribution and governed by its\n# license. Please see the LICENSE file that should have been included\n# as part of this package.\n\n\"\"\"Code for calling and parsing ScanProsite from ExPASy.\"\"\"\n\n# Importing these functions with leading underscore as not intended for reuse\nfrom urllib.request import urlopen\nfrom urllib.parse import urlencode\n\nfrom xml.sax import handler\nfrom xml.sax.expatreader import ExpatParser\n\n\nclass Record(list):\n    \"\"\"Represents search results returned by ScanProsite.\n\n    This record is a list containing the search results returned by\n    ScanProsite. The record also contains the data members n_match,\n    n_seq, capped, and warning.\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        self.n_match = None\n        self.n_seq = None\n        self.capped = None\n        self.warning = None\n\n\n# October 28th 2020 it was recognised that between October 10th 2020 and October\n# 28th the main url of prosite changed from https://www.expasy.org to\n# https://prosite.expasy.org. Thus a change in the mirror was issued from\n# https://www.expasy.org to https://prosite.expasy.org.\ndef scan(seq=\"\", mirror=\"https://prosite.expasy.org\", output=\"xml\", **keywords):\n    \"\"\"Execute a ScanProsite search.\n\n    Arguments:\n     - mirror:   The ScanProsite mirror to be used\n                 (default: https://prosite.expasy.org).\n     - seq:      The query sequence, or UniProtKB (Swiss-Prot,\n                 TrEMBL) accession\n     - output:   Format of the search results\n                 (default: xml)\n\n    Further search parameters can be passed as keywords; see the\n    documentation for programmatic access to ScanProsite at\n    https://prosite.expasy.org/scanprosite/scanprosite_doc.html\n    for a description of such parameters.\n\n    This function returns a handle to the search results returned by\n    ScanProsite. Search results in the XML format can be parsed into a\n    Python object, by using the Bio.ExPASy.ScanProsite.read function.\n\n    \"\"\"\n    parameters = {\"seq\": seq, \"output\": output}\n    for key, value in keywords.items():\n        if value is not None:\n            parameters[key] = value\n    command = urlencode(parameters)\n    url = f\"{mirror}/cgi-bin/prosite/PSScan.cgi?{command}\"\n    handle = urlopen(url)\n    return handle\n\n\ndef read(handle):\n    \"\"\"Parse search results returned by ScanProsite into a Python object.\"\"\"\n    content_handler = ContentHandler()\n    saxparser = Parser()\n    saxparser.setContentHandler(content_handler)\n    saxparser.parse(handle)\n    record = content_handler.record\n    return record\n\n\n# The classes below are considered private\n\n\nclass Parser(ExpatParser):\n    \"\"\"Process the result from a ScanProsite search (PRIVATE).\"\"\"\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        ExpatParser.__init__(self)\n        self.firsttime = True\n\n    def feed(self, data, isFinal=0):\n        \"\"\"Raise an Error if plain text is received in the data.\n\n        This is to show the Error messages returned by ScanProsite.\n        \"\"\"\n        # Error messages returned by the ScanProsite server are formatted as\n        # as plain text instead of an XML document. To catch such error\n        # messages, we override the feed method of the Expat parser.\n        # The error message is (hopefully) contained in the data that was just\n        # fed to the parser.\n        if self.firsttime:\n            if data[:5].decode(\"utf-8\") != \"<?xml\":\n                raise ValueError(data)\n        self.firsttime = False\n        return ExpatParser.feed(self, data, isFinal)\n\n\nclass ContentHandler(handler.ContentHandler):\n    \"\"\"Process and fill in the records, results of the search (PRIVATE).\"\"\"\n\n    integers = (\"start\", \"stop\")\n    strings = (\n        \"sequence_ac\",\n        \"sequence_id\",\n        \"sequence_db\",\n        \"signature_ac\",\n        \"level\",\n        \"level_tag\",\n    )\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        self.element = []\n\n    def startElement(self, name, attrs):\n        \"\"\"Define the beginning of a record and stores the search record.\"\"\"\n        self.element.append(name)\n        self.content = \"\"\n        if self.element == [\"matchset\"]:\n            self.record = Record()\n            self.record.n_match = int(attrs[\"n_match\"])\n            self.record.n_seq = int(attrs[\"n_seq\"])\n        elif self.element == [\"matchset\", \"match\"]:\n            match = {}\n            self.record.append(match)\n\n    def endElement(self, name):\n        \"\"\"Define the end of the search record.\"\"\"\n        assert name == self.element.pop()\n        if self.element == [\"matchset\", \"match\"]:\n            match = self.record[-1]\n            if name in ContentHandler.integers:\n                match[name] = int(self.content)\n            elif name in ContentHandler.strings:\n                match[name] = self.content\n            else:\n                # Unknown type, treat it as a string\n                match[name] = self.content\n\n    def characters(self, content):\n        \"\"\"Store the record content.\"\"\"\n        self.content += content\n", "path": "Bio/ExPASy/ScanProsite.py"}], "after_files": [{"content": "# Copyright 2009 by Michiel de Hoon. All rights reserved.\n# This code is part of the Biopython distribution and governed by its\n# license. Please see the LICENSE file that should have been included\n# as part of this package.\n\n\"\"\"Code for calling and parsing ScanProsite from ExPASy.\"\"\"\n\n# Importing these functions with leading underscore as not intended for reuse\nfrom urllib.request import urlopen\nfrom urllib.parse import urlencode\n\nfrom xml.sax import handler\nfrom xml.sax.expatreader import ExpatParser\n\n\nclass Record(list):\n    \"\"\"Represents search results returned by ScanProsite.\n\n    This record is a list containing the search results returned by\n    ScanProsite. The record also contains the data members n_match,\n    n_seq, capped, and warning.\n    \"\"\"\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        self.n_match = None\n        self.n_seq = None\n        self.capped = None\n        self.warning = None\n\n\n# October 28th 2020 it was recognised that between October 10th 2020 and October\n# 28th the main url of prosite changed from https://www.expasy.org to\n# https://prosite.expasy.org. Thus a change in the mirror was issued from\n# https://www.expasy.org to https://prosite.expasy.org.\ndef scan(seq=\"\", mirror=\"https://prosite.expasy.org\", output=\"xml\", **keywords):\n    \"\"\"Execute a ScanProsite search.\n\n    Arguments:\n     - mirror:   The ScanProsite mirror to be used\n                 (default: https://prosite.expasy.org).\n     - seq:      The query sequence, or UniProtKB (Swiss-Prot,\n                 TrEMBL) accession\n     - output:   Format of the search results\n                 (default: xml)\n\n    Further search parameters can be passed as keywords; see the\n    documentation for programmatic access to ScanProsite at\n    https://prosite.expasy.org/scanprosite/scanprosite_doc.html\n    for a description of such parameters.\n\n    This function returns a handle to the search results returned by\n    ScanProsite. Search results in the XML format can be parsed into a\n    Python object, by using the Bio.ExPASy.ScanProsite.read function.\n\n    \"\"\"\n    parameters = {\"seq\": seq, \"output\": output}\n    for key, value in keywords.items():\n        if value is not None:\n            parameters[key] = value\n    command = urlencode(parameters)\n    url = f\"{mirror}/cgi-bin/prosite/scanprosite/PSScan.cgi?{command}\"\n    handle = urlopen(url)\n    return handle\n\n\ndef read(handle):\n    \"\"\"Parse search results returned by ScanProsite into a Python object.\"\"\"\n    content_handler = ContentHandler()\n    saxparser = Parser()\n    saxparser.setContentHandler(content_handler)\n    saxparser.parse(handle)\n    record = content_handler.record\n    return record\n\n\n# The classes below are considered private\n\n\nclass Parser(ExpatParser):\n    \"\"\"Process the result from a ScanProsite search (PRIVATE).\"\"\"\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        ExpatParser.__init__(self)\n        self.firsttime = True\n\n    def feed(self, data, isFinal=0):\n        \"\"\"Raise an Error if plain text is received in the data.\n\n        This is to show the Error messages returned by ScanProsite.\n        \"\"\"\n        # Error messages returned by the ScanProsite server are formatted as\n        # as plain text instead of an XML document. To catch such error\n        # messages, we override the feed method of the Expat parser.\n        # The error message is (hopefully) contained in the data that was just\n        # fed to the parser.\n        if self.firsttime:\n            if data[:5].decode(\"utf-8\") != \"<?xml\":\n                raise ValueError(data)\n        self.firsttime = False\n        return ExpatParser.feed(self, data, isFinal)\n\n\nclass ContentHandler(handler.ContentHandler):\n    \"\"\"Process and fill in the records, results of the search (PRIVATE).\"\"\"\n\n    integers = (\"start\", \"stop\")\n    strings = (\n        \"sequence_ac\",\n        \"sequence_id\",\n        \"sequence_db\",\n        \"signature_ac\",\n        \"level\",\n        \"level_tag\",\n    )\n\n    def __init__(self):\n        \"\"\"Initialize the class.\"\"\"\n        self.element = []\n\n    def startElement(self, name, attrs):\n        \"\"\"Define the beginning of a record and stores the search record.\"\"\"\n        self.element.append(name)\n        self.content = \"\"\n        if self.element == [\"matchset\"]:\n            self.record = Record()\n            self.record.n_match = int(attrs[\"n_match\"])\n            self.record.n_seq = int(attrs[\"n_seq\"])\n        elif self.element == [\"matchset\", \"match\"]:\n            match = {}\n            self.record.append(match)\n\n    def endElement(self, name):\n        \"\"\"Define the end of the search record.\"\"\"\n        assert name == self.element.pop()\n        if self.element == [\"matchset\", \"match\"]:\n            match = self.record[-1]\n            if name in ContentHandler.integers:\n                match[name] = int(self.content)\n            elif name in ContentHandler.strings:\n                match[name] = self.content\n            else:\n                # Unknown type, treat it as a string\n                match[name] = self.content\n\n    def characters(self, content):\n        \"\"\"Store the record content.\"\"\"\n        self.content += content\n", "path": "Bio/ExPASy/ScanProsite.py"}]}