prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  :arrow_up: Update MeiliSearch
  ### What needs to be documented?
  
  We should update MeiliSearch to version 0.25. This means we have to change how we manage API keys. It does mean we can create more keys for monitoring and the like.
  
  ### Additional context
  
  [Blog post on version](https://blog.meilisearch.com/whats-new-in-v0-25/)
  [Additional context](https://github.com/orgs/platformsh/projects/3)
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `search/main.py`
  Content:
  ```
  1 import os
  2 import glob
  3 import json
  4 import meilisearch
  5 from platformshconfig import Config
  6 
  7 class Search:
  8     def __init__(self):
  9         self.default = {
  10             "host": "http://127.0.0.1",
  11             "key": None,
  12             "port": 7700
  13         }
  14 
  15         self.scrape_dir = "output"
  16         self.scrape_config = "config/scrape.json"
  17         self.docs_index = "docs"
  18         self.primaryKey = "documentId"
  19         self.index_name = "Docs"
  20 
  21         # Below are Platform.sh custom settings for how the search engine functions.
  22 
  23         # Data available to the dropdown React app in docs, used to fill out autocomplete results.
  24         self.displayed_attributes = ['keywords', 'title', 'text', 'url', 'site', 'section']
  25         # Data actually searchable by our queries.
  26         self.searchable_attributes = ['keywords', 'title', 'pageUrl', 'section', 'text', 'url']
  27 
  28         # Show results for one query with the listed pages, when they by default would not show up as best results.
  29         # Note: these aren't automatically two-way, which is why they're all defined twice.
  30         self.synonyms = {
  31             "cron": ["crons"],
  32             "crons": ["cron tasks", "cron jobs"],
  33             "e-mail": ["email"],
  34             "routes.yaml": ["routes"],
  35             "routes": ["routes.yaml"],
  36             "services": ["services.yaml"],
  37             "services.yaml": ["services"],
  38             "application": [".platform.app.yaml", "app.yaml", "applications.yaml"],
  39             ".platform.app.yaml": ["application"],
  40             "app.yaml": ["application"],
  41             "applications.yaml": ["application", "multi-app"],
  42             "multi-app": ["applications.yaml"],
  43             "regions": ["public ip addresses"],
  44             "public ip addresses": ["regions"],
  45             "ssl": ["https", "tls"],
  46             "https": ["ssl"],
  47             "auth": ["authentication", "access control"], # Only needs to be one way since we don't use "auth" in the docs
  48         }
  49 
  50         # Ranking rules:
  51         #
  52         #   - Default order: ["words", "typo", "proximity", "attribute", "sort", "exactness"]
  53         #
  54         #   - words: number of times query is in document (greater number gets priority)
  55         #   - typo: fewer typos > more typos
  56         #   - proximity: smaller distance between multiple occurences of query in same document > larger distances
  57         #   - attribute: sorted according to order of importance of attributes (searchable_attributes). terms in
  58         #       more important attributes first.
  59         #   - sort: queries are sorted at query time
  60         #   - exactness: similarity of matched words in document with query
  61 
  62         self.ranking_rules = ["rank:asc", "attribute", "typo", "words", "proximity", "exactness"]
  63 
  64         self.updated_settings = {
  65             "rankingRules": self.ranking_rules,
  66             "searchableAttributes": self.searchable_attributes,
  67             "displayedAttributes": self.displayed_attributes
  68         }
  69 
  70         # Group results by page
  71         self.distinct_attribute = "pageUrl"
  72 
  73     def getConnectionString(self):
  74         """
  75         Sets the Meilisearch host string, depending on the environment.
  76 
  77         Returns:
  78             string: Meilisearch host string.
  79         """
  80         if os.environ.get('PORT'):
  81             return "{}:{}".format(self.default["host"], os.environ['PORT'])
  82         else:
  83             return "{}:{}".format(self.default["host"], self.default["port"])
  84 
  85     def getMasterKey(self):
  86         """
  87         Retrieves the Meilisearch master key, either from the Platform.sh environment or locally.
  88         """
  89         config = Config()
  90         if config.is_valid_platform():
  91             return config.projectEntropy
  92         elif os.environ.get("MEILI_MASTER_KEY"):
  93             return os.environ["MEILI_MASTER_KEY"]
  94         else:
  95             return self.default["key"]
  96 
  97     def add_documents(self, index):
  98         """
  99         Cycle through the individual site indexes in /outputs so their individual documents can be added to Meilisearch.
  100         """
  101         documents = [f for f in glob.glob("{}/*.json".format(self.scrape_dir))]
  102         for doc in documents:
  103             self.add(doc, index)
  104 
  105     def add(self, doc, index):
  106         """
  107         Add an individual site's index to the Meilisearch service.
  108         """
  109         with open(doc) as scraped_index:
  110             data = json.load(scraped_index)
  111             index.add_documents(data)
  112 
  113     def update(self):
  114         """
  115         Updates the Meilisearch index.
  116         """
  117         # Create a Meilisearch client.
  118         client = meilisearch.Client(self.getConnectionString(), self.getMasterKey())
  119 
  120         # Delete previous index
  121         if len(client.get_indexes()):
  122             client.get_index(self.docs_index).delete()
  123 
  124         # Create a new index
  125         index = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})
  126 
  127         # Add synonyms for the index
  128         index.update_synonyms(self.synonyms)
  129 
  130         # Update its settings: what can be searched, what's displayable, and how results should be ranked.
  131         index.update_settings(self.updated_settings)
  132 
  133         # Update distinct attribute.
  134         index.update_distinct_attribute(self.distinct_attribute)
  135 
  136         # Add documents to the index
  137         self.add_documents(index)
  138 
  139 if __name__ == "__main__":
  140     meili = Search()
  141     meili.update()
  142
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/search/main.py b/search/main.py
  --- a/search/main.py
  +++ b/search/main.py
  @@ -119,10 +119,14 @@
   
           # Delete previous index
           if len(client.get_indexes()):
  -            client.get_index(self.docs_index).delete()
  +            client.index(self.docs_index).delete()
   
           # Create a new index
  -        index = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})
  +        create_index_task = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})
  +
  +        client.wait_for_task(create_index_task['uid'])
  +
  +        index = client.get_index(create_index_task['indexUid'])
   
           # Add synonyms for the index
           index.update_synonyms(self.synonyms)
  
verification_info: {"golden_diff": "diff --git a/search/main.py b/search/main.py\n--- a/search/main.py\n+++ b/search/main.py\n@@ -119,10 +119,14 @@\n \n         # Delete previous index\n         if len(client.get_indexes()):\n-            client.get_index(self.docs_index).delete()\n+            client.index(self.docs_index).delete()\n \n         # Create a new index\n-        index = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})\n+        create_index_task = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})\n+\n+        client.wait_for_task(create_index_task['uid'])\n+\n+        index = client.get_index(create_index_task['indexUid'])\n \n         # Add synonyms for the index\n         index.update_synonyms(self.synonyms)\n", "issue": ":arrow_up: Update MeiliSearch\n### What needs to be documented?\r\n\r\nWe should update MeiliSearch to version 0.25. This means we have to change how we manage API keys. It does mean we can create more keys for monitoring and the like.\r\n\r\n### Additional context\r\n\r\n[Blog post on version](https://blog.meilisearch.com/whats-new-in-v0-25/)\r\n[Additional context](https://github.com/orgs/platformsh/projects/3)\n", "before_files": [{"content": "import os\nimport glob\nimport json\nimport meilisearch\nfrom platformshconfig import Config\n\nclass Search:\n    def __init__(self):\n        self.default = {\n            \"host\": \"http://127.0.0.1\",\n            \"key\": None,\n            \"port\": 7700\n        }\n\n        self.scrape_dir = \"output\"\n        self.scrape_config = \"config/scrape.json\"\n        self.docs_index = \"docs\"\n        self.primaryKey = \"documentId\"\n        self.index_name = \"Docs\"\n\n        # Below are Platform.sh custom settings for how the search engine functions.\n\n        # Data available to the dropdown React app in docs, used to fill out autocomplete results.\n        self.displayed_attributes = ['keywords', 'title', 'text', 'url', 'site', 'section']\n        # Data actually searchable by our queries.\n        self.searchable_attributes = ['keywords', 'title', 'pageUrl', 'section', 'text', 'url']\n\n        # Show results for one query with the listed pages, when they by default would not show up as best results.\n        # Note: these aren't automatically two-way, which is why they're all defined twice.\n        self.synonyms = {\n            \"cron\": [\"crons\"],\n            \"crons\": [\"cron tasks\", \"cron jobs\"],\n            \"e-mail\": [\"email\"],\n            \"routes.yaml\": [\"routes\"],\n            \"routes\": [\"routes.yaml\"],\n            \"services\": [\"services.yaml\"],\n            \"services.yaml\": [\"services\"],\n            \"application\": [\".platform.app.yaml\", \"app.yaml\", \"applications.yaml\"],\n            \".platform.app.yaml\": [\"application\"],\n            \"app.yaml\": [\"application\"],\n            \"applications.yaml\": [\"application\", \"multi-app\"],\n            \"multi-app\": [\"applications.yaml\"],\n            \"regions\": [\"public ip addresses\"],\n            \"public ip addresses\": [\"regions\"],\n            \"ssl\": [\"https\", \"tls\"],\n            \"https\": [\"ssl\"],\n            \"auth\": [\"authentication\", \"access control\"], # Only needs to be one way since we don't use \"auth\" in the docs\n        }\n\n        # Ranking rules:\n        #\n        #   - Default order: [\"words\", \"typo\", \"proximity\", \"attribute\", \"sort\", \"exactness\"]\n        #\n        #   - words: number of times query is in document (greater number gets priority)\n        #   - typo: fewer typos > more typos\n        #   - proximity: smaller distance between multiple occurences of query in same document > larger distances\n        #   - attribute: sorted according to order of importance of attributes (searchable_attributes). terms in\n        #       more important attributes first.\n        #   - sort: queries are sorted at query time\n        #   - exactness: similarity of matched words in document with query\n\n        self.ranking_rules = [\"rank:asc\", \"attribute\", \"typo\", \"words\", \"proximity\", \"exactness\"]\n\n        self.updated_settings = {\n            \"rankingRules\": self.ranking_rules,\n            \"searchableAttributes\": self.searchable_attributes,\n            \"displayedAttributes\": self.displayed_attributes\n        }\n\n        # Group results by page\n        self.distinct_attribute = \"pageUrl\"\n\n    def getConnectionString(self):\n        \"\"\"\n        Sets the Meilisearch host string, depending on the environment.\n\n        Returns:\n            string: Meilisearch host string.\n        \"\"\"\n        if os.environ.get('PORT'):\n            return \"{}:{}\".format(self.default[\"host\"], os.environ['PORT'])\n        else:\n            return \"{}:{}\".format(self.default[\"host\"], self.default[\"port\"])\n\n    def getMasterKey(self):\n        \"\"\"\n        Retrieves the Meilisearch master key, either from the Platform.sh environment or locally.\n        \"\"\"\n        config = Config()\n        if config.is_valid_platform():\n            return config.projectEntropy\n        elif os.environ.get(\"MEILI_MASTER_KEY\"):\n            return os.environ[\"MEILI_MASTER_KEY\"]\n        else:\n            return self.default[\"key\"]\n\n    def add_documents(self, index):\n        \"\"\"\n        Cycle through the individual site indexes in /outputs so their individual documents can be added to Meilisearch.\n        \"\"\"\n        documents = [f for f in glob.glob(\"{}/*.json\".format(self.scrape_dir))]\n        for doc in documents:\n            self.add(doc, index)\n\n    def add(self, doc, index):\n        \"\"\"\n        Add an individual site's index to the Meilisearch service.\n        \"\"\"\n        with open(doc) as scraped_index:\n            data = json.load(scraped_index)\n            index.add_documents(data)\n\n    def update(self):\n        \"\"\"\n        Updates the Meilisearch index.\n        \"\"\"\n        # Create a Meilisearch client.\n        client = meilisearch.Client(self.getConnectionString(), self.getMasterKey())\n\n        # Delete previous index\n        if len(client.get_indexes()):\n            client.get_index(self.docs_index).delete()\n\n        # Create a new index\n        index = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})\n\n        # Add synonyms for the index\n        index.update_synonyms(self.synonyms)\n\n        # Update its settings: what can be searched, what's displayable, and how results should be ranked.\n        index.update_settings(self.updated_settings)\n\n        # Update distinct attribute.\n        index.update_distinct_attribute(self.distinct_attribute)\n\n        # Add documents to the index\n        self.add_documents(index)\n\nif __name__ == \"__main__\":\n    meili = Search()\n    meili.update()\n", "path": "search/main.py"}], "after_files": [{"content": "import os\nimport glob\nimport json\nimport meilisearch\nfrom platformshconfig import Config\n\nclass Search:\n    def __init__(self):\n        self.default = {\n            \"host\": \"http://127.0.0.1\",\n            \"key\": None,\n            \"port\": 7700\n        }\n\n        self.scrape_dir = \"output\"\n        self.scrape_config = \"config/scrape.json\"\n        self.docs_index = \"docs\"\n        self.primaryKey = \"documentId\"\n        self.index_name = \"Docs\"\n\n        # Below are Platform.sh custom settings for how the search engine functions.\n\n        # Data available to the dropdown React app in docs, used to fill out autocomplete results.\n        self.displayed_attributes = ['keywords', 'title', 'text', 'url', 'site', 'section']\n        # Data actually searchable by our queries.\n        self.searchable_attributes = ['keywords', 'title', 'pageUrl', 'section', 'text', 'url']\n\n        # Show results for one query with the listed pages, when they by default would not show up as best results.\n        # Note: these aren't automatically two-way, which is why they're all defined twice.\n        self.synonyms = {\n            \"cron\": [\"crons\"],\n            \"crons\": [\"cron tasks\", \"cron jobs\"],\n            \"e-mail\": [\"email\"],\n            \"routes.yaml\": [\"routes\"],\n            \"routes\": [\"routes.yaml\"],\n            \"services\": [\"services.yaml\"],\n            \"services.yaml\": [\"services\"],\n            \"application\": [\".platform.app.yaml\", \"app.yaml\", \"applications.yaml\"],\n            \".platform.app.yaml\": [\"application\"],\n            \"app.yaml\": [\"application\"],\n            \"applications.yaml\": [\"application\", \"multi-app\"],\n            \"multi-app\": [\"applications.yaml\"],\n            \"regions\": [\"public ip addresses\"],\n            \"public ip addresses\": [\"regions\"],\n            \"ssl\": [\"https\", \"tls\"],\n            \"https\": [\"ssl\"],\n            \"auth\": [\"authentication\", \"access control\"], # Only needs to be one way since we don't use \"auth\" in the docs\n        }\n\n        # Ranking rules:\n        #\n        #   - Default order: [\"words\", \"typo\", \"proximity\", \"attribute\", \"sort\", \"exactness\"]\n        #\n        #   - words: number of times query is in document (greater number gets priority)\n        #   - typo: fewer typos > more typos\n        #   - proximity: smaller distance between multiple occurences of query in same document > larger distances\n        #   - attribute: sorted according to order of importance of attributes (searchable_attributes). terms in\n        #       more important attributes first.\n        #   - sort: queries are sorted at query time\n        #   - exactness: similarity of matched words in document with query\n\n        self.ranking_rules = [\"rank:asc\", \"attribute\", \"typo\", \"words\", \"proximity\", \"exactness\"]\n\n        self.updated_settings = {\n            \"rankingRules\": self.ranking_rules,\n            \"searchableAttributes\": self.searchable_attributes,\n            \"displayedAttributes\": self.displayed_attributes\n        }\n\n        # Group results by page\n        self.distinct_attribute = \"pageUrl\"\n\n    def getConnectionString(self):\n        \"\"\"\n        Sets the Meilisearch host string, depending on the environment.\n\n        Returns:\n            string: Meilisearch host string.\n        \"\"\"\n        if os.environ.get('PORT'):\n            return \"{}:{}\".format(self.default[\"host\"], os.environ['PORT'])\n        else:\n            return \"{}:{}\".format(self.default[\"host\"], self.default[\"port\"])\n\n    def getMasterKey(self):\n        \"\"\"\n        Retrieves the Meilisearch master key, either from the Platform.sh environment or locally.\n        \"\"\"\n        config = Config()\n        if config.is_valid_platform():\n            return config.projectEntropy\n        elif os.environ.get(\"MEILI_MASTER_KEY\"):\n            return os.environ[\"MEILI_MASTER_KEY\"]\n        else:\n            return self.default[\"key\"]\n\n    def add_documents(self, index):\n        \"\"\"\n        Cycle through the individual site indexes in /outputs so their individual documents can be added to Meilisearch.\n        \"\"\"\n        documents = [f for f in glob.glob(\"{}/*.json\".format(self.scrape_dir))]\n        for doc in documents:\n            self.add(doc, index)\n\n    def add(self, doc, index):\n        \"\"\"\n        Add an individual site's index to the Meilisearch service.\n        \"\"\"\n        with open(doc) as scraped_index:\n            data = json.load(scraped_index)\n            index.add_documents(data)\n\n    def update(self):\n        \"\"\"\n        Updates the Meilisearch index.\n        \"\"\"\n        # Create a Meilisearch client.\n        client = meilisearch.Client(self.getConnectionString(), self.getMasterKey())\n\n        # Delete previous index\n        if len(client.get_indexes()):\n            client.index(self.docs_index).delete()\n\n        # Create a new index\n        create_index_task = client.create_index(uid=self.docs_index, options={'primaryKey': self.primaryKey, 'uid': self.index_name})\n\n        client.wait_for_task(create_index_task['uid'])\n\n        index = client.get_index(create_index_task['indexUid'])\n\n        # Add synonyms for the index\n        index.update_synonyms(self.synonyms)\n\n        # Update its settings: what can be searched, what's displayable, and how results should be ranked.\n        index.update_settings(self.updated_settings)\n\n        # Update distinct attribute.\n        index.update_distinct_attribute(self.distinct_attribute)\n\n        # Add documents to the index\n        self.add_documents(index)\n\nif __name__ == \"__main__\":\n    meili = Search()\n    meili.update()\n", "path": "search/main.py"}]}