GroupDocs.Search for Python via .NET is designed to work with AI agents, LLMs, and code generation tools. The library ships machine-readable documentation in several formats, including an AGENTS.md file inside the pip package itself, so that AI assistants can discover and use the API without manual guidance.
Built into the package
The groupdocs-search-net pip package includes an AGENTS.md file at groupdocs/search/AGENTS.md. AI coding assistants that scan installed packages (such as Claude Code, Cursor, and GitHub Copilot) can read it to learn the imports, the index lifecycle, the query language, events, callbacks, licensing, platform requirements, and troubleshooting tips. Its full text is reproduced below.
After installing the package, you can find it with:
pip show -f groupdocs-search-net | grep AGENTS
MCP server
GroupDocs provides an MCP (Model Context Protocol) server that lets LLMs query the documentation on demand instead of loading it all at once. This saves tokens and lets your AI assistant fetch only what it needs for the current task.
To connect your AI tool to the MCP server, add the GroupDocs endpoint to your MCP configuration:
// Claude Code: ~/.claude/settings.json (or project .mcp.json)
// Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
// .vscode/mcp.json in your project root
{"servers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
// .cursor/mcp.json in your project root
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
// Any MCP-compatible client
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}
The GroupDocs.Search MCP server is a separate server that does the work itself: it lets an AI agent index a folder of documents and search it by content, locally on your machine, then quote the matching passages. The server is the .NET build of GroupDocs.Search and ships as a Docker image, so it runs alongside a Python project without a .NET installation.
Use the documentation MCP server when an agent writes Python code against this library, and the search MCP server when the agent itself looks things up in your documents.
Machine-readable documentation
Every documentation page is available as a plain Markdown file that AI tools can fetch and process directly:
Point your AI assistant to the full documentation file for comprehensive context:
Fetch https://docs.groupdocs.com/search/python-net/llms-full.txt and use it
as a reference for GroupDocs.Search for Python via .NET API.
Why GroupDocs.Search is a good building block for AI pipelines
An agent that answers questions about your documents first has to find the right ones, and then the right passages in them. GroupDocs.Search keeps a full-text index of a folder on your own disk and answers both questions, across PDF, Office, OpenDocument, email, eBook, archive and image files:
Retrieve for RAG: find documents by keyword, phrase, wildcard, regular expression, boolean and field queries, with a relevance score for each.
Quote the evidence: get the passages around each found word, with the found words marked, ready to place in a prompt.
Forgive the query: fuzzy search tolerates typos and OCR noise, and synonym, homophone and word-form search find what the user meant.
Stay current: add() and update() index only the documents that are new or changed, and the index persists between runs.
Read what is inside: index the documents in ZIP archives and email attachments, and connect your own OCR engine for scans.
A typical retrieval step looks like this:
importjsonimportosfromgroupdocs.searchimportIndex,IndexSettingsfromgroupdocs.search.highlightersimportFragmentHighlighterfromgroupdocs.search.optionsimportCompression,HighlightOptions,OutputFormat,TextStorageSettings# Keep the extracted text in the index, so passages can be quoted without the original filessettings=IndexSettings()settings.text_storage_settings=TextStorageSettings(Compression.HIGH)index=Index("./index",settings)index.add("./documents")# On later runs, only new and changed documents are indexedresult=index.search("termination AND penalty")# Words of context around each found word, and how the found words are markedoptions=HighlightOptions()options.terms_before=8options.terms_after=8options.terms_total=30options.term_highlight_start_tag="**"options.term_highlight_end_tag="**"found=sorted(result,key=lambdadocument:document.relevance,reverse=True)passages=[]fordocumentinfound[:5]:# The five most relevant documentshighlighter=FragmentHighlighter(OutputFormat.PLAIN_TEXT)index.highlight(document,highlighter,options)fragments=[fragmentforcontainerinhighlighter.get_result()forfragmentincontainer.get_fragments()]passages.append({"file":os.path.basename(document.document_info.file_path),"occurrences":document.occurrence_count,"passages":fragments,})# Feed `passages` into an agent's context or a RAG promptprint(json.dumps(passages,indent=2))
For three short contracts, two of which mention a termination penalty, it prints (abridged):
[
{
"file": "supply-contract.txt",
"occurrences": 4,
"passages": [
" end this agreement with ninety days notice.\nEarly **termination** by the buyer carries a **penalty** of 12 percent of the remaining order value",
".\nThe **termination** **penalty** is payable within thirty days of the notice"
]
},
...
]
Without a license, an index holds at most 100 documents and only the first 10 found documents can be read. Set the GROUPDOCS_LIC_PATH environment variable before an agent runs these steps. See Licensing and evaluation.
Pair with other GroupDocs products
For richer AI document pipelines, combine GroupDocs.Search with:
The complete AGENTS.md shipped in the 26.10 package:
# GroupDocs.Search for Python via .NET -- AGENTS.md
> Instructions for AI agents working with this package.
Full-text search and indexing -- build a persistent index over a folder of documents and run keyword, phrase, wildcard, regular-expression, fuzzy, boolean, faceted, synonym, and homophone queries against it. Works across Word, Excel, PowerPoint, PDF, email, eBook, web, archive and plain-text formats through one unified API, with no MS Office or external software installed.
## Install
```bash
pip install groupdocs-search-net
```Each release's wheels are also on https://releases.groupdocs.com/search/python-net/ for offline installs (`pip install <wheelfile>`).
**Python**: 3.5 - 3.14 | **Platforms**: Windows x64, Linux x64 (glibc 2.27+), macOS 13+ (Intel and Apple Silicon)
## Resources
| Resource | URL |
|---|---|
| Documentation | https://docs.groupdocs.com/search/python-net/ |
| LLM-optimized docs | https://docs.groupdocs.com/search/python-net/llms-full.txt |
| API reference | https://reference.groupdocs.com/search/python-net/ |
| Code examples | https://github.com/groupdocs-search/GroupDocs.Search-for-Python-via-.NET |
| Release notes | https://releases.groupdocs.com/search/python-net/release-notes/ |
| PyPI | https://pypi.org/project/groupdocs-search-net/ |
| Free support forum | https://forum.groupdocs.com/c/search/ |
| Temporary license | https://purchase.groupdocs.com/temporary-license |
## MCP Server
If your environment has MCP configured, you can connect your AI tool to the GroupDocs documentation server for on-demand API lookups:
```json
{"mcpServers":{"groupdocs-docs":{"url":"https://docs.groupdocs.com/mcp"}}}```Works with Claude Code (`~/.claude/settings.json`), Cursor (`.cursor/mcp.json`), VS Code Copilot (`.vscode/mcp.json`), and any MCP-compatible client. If MCP is unavailable, fall back to the LLM-optimized docs URL above and this file -- both are shipped inside the wheel.
## Imports
```python
fromgroupdocs.searchimport(Index,IndexSettings,IndexRepository,IndexUpdater,Extractor,ExtractionHost,SearchQuery,License,Metered,LicenseError,)fromgroupdocs.search.optionsimport(SearchOptions,FuzzySearchOptions,SimilarityLevel,TableDiscreteFunction,IndexingOptions,UpdateOptions,MergeOptions,OptimizeOptions,DeleteOptions,HighlightOptions,OutputFormat,IndexType,CommonFieldNames,SpellingCorrectorOptions,ImageSearchOptions,)fromgroupdocs.search.resultsimport(SearchResult,FoundDocument,FoundDocumentField,DocumentInfo,FileType,DeleteResult,FormatFamily,)fromgroupdocs.search.commonimport(Document,DocumentField,ExtractedItemInfo,IndexInfo,IndexStatus,IndexingReport,SearchReport,OutputAdapter,FileOutputAdapter,StringOutputAdapter,StreamOutputAdapter,)fromgroupdocs.search.highlightersimportHighlighter,DocumentHighlighter,FragmentHighlighterfromgroupdocs.search.dictionariesimport(DictionaryRepository,SynonymDictionary,HomophoneDictionary,SpellingCorrector,AliasDictionary,StopWordDictionary,)fromgroupdocs.search.eventsimportEventHub,FileIndexingEventArgsfromgroupdocs.search.scalingimportIndexer,Searcher,SearchNetworkNode,SearchNetworkStatusfromgroupdocs.search.exceptionsimportSearchException,LicenseRestrictionException```## Index lifecycle (the core workflow)
`Index` is the entry point. The flow is always: **create/open an index → `add(...)` documents → `search(...)`**. An index is a folder on disk, so you build it once and reopen it on a later run by pointing `Index(...)` at the same folder. `update()` re-indexes only what changed; `optimize()` compacts the index for faster search.
```python
fromgroupdocs.searchimportIndexindex=Index("./index")# create, or reopen an existing index in this folderindex.add("./documents")# extract text + index every supported file in the folderresult=index.search("contract")# returns a SearchResultprint(result.document_count,result.occurrence_count)```**`Index(...)` constructor.** `Index(index_folder)` opens/creates an on-disk index. `Index(index_folder, IndexSettings(...))` configures it (in-memory index, stop words, index type, document filter, logger, custom extractors, …). Use `IndexRepository` to manage several indexes together. `Index` is disposable -- it works as a context manager (`with Index(...) as index:`) and has `dispose()`.
**Settings before documents.** Since 26.10, the dictionaries (`index.dictionaries`) and the settings that split text into words can change only while the index is empty. Changing one after `add()` raises "... cannot be changed because the index ... contains documents".
**`add(...)`** indexes a folder path or a single file path, a list of paths, a list of `Document` objects (`Document.create_from_file`, `create_from_stream`, `create_from_structure`, `create_lazy`), or extracted data, with optional `IndexingOptions` (`threads`, `is_async`, OCR and image indexing options). A file it cannot read does not raise: it is left out and reported through `index.events.error_occurred`.
**`search(query [, SearchOptions])`** parses a text query (see Query language) and returns a `SearchResult`: `.document_count`, `.occurrence_count`, iteration (`for found in result`), `.get_found_document(i)`, plus `.truncated`, `.warnings`, `.search_duration`.
## Query language
A single text query string can combine all of these:
| Form | Example | Meaning |
|---|---|---|
| Keyword | `Lorem` | match a word |
| Phrase | `"Lorem ipsum"` | match an exact phrase |
| Wildcard | `?orem`, `Lo*` | `?` = one character, `*` = any run |
| Regex | `^Lo.*` | regular expression (leading `^`) |
| Boolean | `Lorem AND ipsum`, `a OR b`, `a AND NOT b` | combine terms |
| Faceted | `Content: Lorem`, `FileName: report` | restrict to a field |
`SearchQuery` also builds queries programmatically (`SearchQuery.create_word_query`, `create_phrase_search_query`, `create_and_query`, `create_regex_query`, `create_wildcard_query`, `create_field_query`, …) for cases where string parsing is awkward. `create_and_query` / `create_or_query` take two or more queries, as separate arguments or one list. `create_regex_query(pattern, options)` takes `RegexOptions` from `groupdocs.search` (`RegexOptions.IGNORE_CASE | RegexOptions.CULTURE_INVARIANT`), which must include `IGNORE_CASE`; a Python `re` flag such as `re.IGNORECASE` is translated.
## Operations
### Keyword / phrase / wildcard / regex / boolean search
```python
fromgroupdocs.searchimportIndexindex=Index("./index")index.add("./documents")forqueryin["contract",'"service agreement"',"?ontract","^cont.*","contract AND NOT draft"]:print(query,index.search(query).document_count)```### Fuzzy search (tolerate typos / OCR noise)
```python
fromgroupdocs.searchimportIndexfromgroupdocs.search.optionsimportSearchOptions,SimilarityLevelindex=Index("./index")index.add("./documents")options=SearchOptions()options.fuzzy_search.enabled=Trueoptions.fuzzy_search.fuzzy_algorithm=SimilarityLevel(0.8)# 0.0 loose .. 1.0 strictoptions.fuzzy_search.only_best_results=True# optionalresult=index.search("contarct",options)# matches "contract"print(result.document_count)````fuzzy_algorithm` accepts `SimilarityLevel(level)` or `TableDiscreteFunction(...)` for a custom edit-distance-by-length table.
### Synonym, homophone, word-forms and case-sensitive search
```python
fromgroupdocs.searchimportIndexfromgroupdocs.search.optionsimportSearchOptionsindex=Index("./index")index.add("./documents")options=SearchOptions()options.use_synonym_search=True# "make" also matches "create", "produce", …options.use_homophone_search=True# "right" also matches "write"options.use_word_forms_search=True# "contract" also matches "contracts"print(index.search("contract",options).document_count)case_sensitive=SearchOptions()case_sensitive.use_case_sensitive_search=Trueprint(index.search("Service",case_sensitive).document_count)```Case-sensitive search does not combine with fuzzy, synonym, homophone or word-forms search, spelling or keyboard-layout correction: together they return an empty result, and `error_occurred` names the conflict.
Customize the dictionaries through `index.dictionaries` (a `DictionaryRepository`) before the first `add()`: `synonym_dictionary`, `homophone_dictionary`, `spelling_corrector`, `alias_dictionary`, `stop_word_dictionary`, `alphabet`, `character_replacements`, `word_forms_provider`. Each dictionary supports `import_dictionary(path)` / `export_dictionary(path)` / `clear()`.
### Inspect search results
```python
fromgroupdocs.searchimportIndexindex=Index("./index")index.add("./documents")result=index.search("contract")print("documents:",result.document_count,"occurrences:",result.occurrence_count)forfoundinresult:# FoundDocumentprint(found.document_info.file_path,found.occurrence_count,found.relevance)print("fields:",[f.field_nameforfinfound.found_fields])```### Highlight matches
```python
fromgroupdocs.searchimportIndexfromgroupdocs.search.commonimportFileOutputAdapter,StringOutputAdapterfromgroupdocs.search.highlightersimportDocumentHighlighter,FragmentHighlighterfromgroupdocs.search.optionsimportOutputFormatindex=Index("./index")index.add("./documents")found=index.search("contract").get_found_document(0)# (a) whole document -> HTML fileindex.highlight(found,DocumentHighlighter(FileOutputAdapter(OutputFormat.HTML,"./out.html")))# (b) whole document -> in-memory stringadapter=StringOutputAdapter(OutputFormat.PLAIN_TEXT)index.highlight(found,DocumentHighlighter(adapter))text=adapter.get_result()# (c) just the matching fragmentsfh=FragmentHighlighter(OutputFormat.HTML)index.highlight(found,fh)forcontainerinfh.get_result():print(container.field_name,container.count)````OutputFormat` is `HTML` or `PLAIN_TEXT`. Output adapters: `FileOutputAdapter(format, path)`, `StringOutputAdapter(format)`, `StreamOutputAdapter(format, stream)`.
### List indexed documents and extract their text
```python
fromgroupdocs.searchimportIndexfromgroupdocs.search.commonimportStringOutputAdapterfromgroupdocs.search.optionsimportOutputFormatindex=Index("./index")index.add("./documents")fordinindex.get_indexed_documents():# list of DocumentInfoprint(d.file_path)print(index.get_indexed_paths())# the added rootsadapter=StringOutputAdapter(OutputFormat.PLAIN_TEXT)index.get_document_text(index.get_indexed_documents()[0],adapter)print(adapter.get_result()[:200])```### Keep the index up to date
```python
importosfromgroupdocs.searchimportIndexfromgroupdocs.search.optionsimportUpdateOptionsindex=Index("./index")index.add("./documents")index.update()# re-index only documents changed since the last add/updateindex.optimize()# merge segments for faster subsequent searches# delete() matches the paths the index stores, which are absoluteresult=index.delete([os.path.abspath("./documents/draft-contract.txt")],UpdateOptions())print(result.success_count)```### Reports & diagnostics
```python
fromgroupdocs.searchimportIndexindex=Index("./index")index.add("./documents")index.search("contract")forrinindex.get_indexing_reports():print(len(r.indexed_documents),r.indexing_time)# IndexingReport, one per batchforrinindex.get_search_reports():print(r.text_query,r.document_count)# SearchReport, one per queryprint(index.index_info.index_folder)# IndexInfo```## Events
`index.events` (an `EventHub`) raises .NET events; subscribe with `+=`, and a handler takes `(sender, args)`. `-=` removes the last subscription of that handler, `clear()` removes every handler added from Python, and `index.events.file_indexing = handler` raises `AttributeError` (use `+=`).
```python
fromgroupdocs.searchimportIndexindex=Index("./index")defon_file_indexing(sender,args):# args: FileIndexingEventArgsifargs.document_full_path.endswith(".tmp"):args.skip_indexing=True# leave the file outindex.events.file_indexing+=on_file_indexingindex.events.operation_progress_changed+=lambdasender,args:print(args.processed_documents,"of",args.total_documents)index.events.error_occurred+=lambdasender,args:print("not indexed:",args.message[:80])index.add("./documents")index.events.file_indexing-=on_file_indexing```Events on `index.events`: `file_indexing`, `operation_progress_changed`, `operation_finished`, `optimization_progress_changed`, `error_occurred`, `password_required` (set `args.password`), `image_preparing`, `status_changed`, `search_phase_completed`. `Extractor` has `error_occurred`, `image_preparing`, `password_required`; the scaling `NodeEventHub` has the search-network events.
**Threads.** A handler runs on the thread that raises the event. With `IndexingOptions.threads` above 1 every `file_indexing` event runs on an engine thread, and with `is_async = True` (the call returns at once) every event does: wait for `operation_finished` with a `threading.Event`. An exception a handler raises on an engine thread is printed to stderr and ignored; on the calling thread it reaches the engine, which skips that document and reports it through `error_occurred`.
## Callbacks (interfaces as Python objects)
Write a plain class with the snake_case methods; do not subclass the generated interface class. One-method interfaces also take a function.
| Interface | Where it goes | Python shape |
|---|---|---|
| `ILogger` | `IndexSettings.logger` | `error(message)`, `trace(message)`; or `fn(level, message)` |
| `IFieldExtractor` | `IndexSettings.custom_extractors.add(...)`, `args.custom_extractor` | `extensions = [".log"]`, `get_fields(source)` returning `DocumentField`s or `(name, value)` pairs |
| `IContainerItemExtractor` | as above | plus `format_family = FormatFamily.ARCHIVE`, `get_container_items(source)` returning `ExtractedItemInfo`s or `(format_family, fields, item_info, inner_path)` |
| `IDocumentLoader` | `Document.create_lazy(kind, key, loader)` | `load_document()` returning a `Document`, `close_document()` |
| `IWordFormsProvider` | `index.dictionaries.word_forms_provider` | `get_word_forms(word)` -> list of str |
| `IWordSplitter` | `args.word_splitter` in `file_indexing` | `split(text)` -> iterable of str |
| `IOcrConnector` | `IndexingOptions.ocr_indexing_options.ocr_connector` | `recognize(context)` -> str or None; `context.image_stream.read()` |
`source` in `get_fields`/`get_container_items` is the file's path (`str`) or the engine's stream (`source.read()` -> bytes), valid during the call. `extensions` and `format_family` are read once, when the extractor is added. A lazy document calls its loader for `DocumentSourceKind.STREAM` and `STRUCTURE`; a `FILE` lazy document is read from its key as a path.
```python
fromgroupdocs.searchimportIndex,IndexSettingsclassLogExtractor(object):extensions=[".log"]defget_fields(self,source):text=open(source,encoding="utf-8").read()ifisinstance(source,str)elsesource.read().decode("utf-8")return[("Content",text)]classConsoleLogger(object):deferror(self,message):print("ERROR",message)deftrace(self,message):passsettings=IndexSettings()settings.custom_extractors.add(LogExtractor())settings.logger=ConsoleLogger()index=Index("./index",settings)index.add("./logs")print(index.search("timeout").document_count)```## Licensing
```python
fromgroupdocs.searchimportLicenseLicense().set_license("path/to/license.lic")# from a filewithopen("path/to/license.lic","rb")asf:# or from a streamLicense().set_license(f)```Or auto-apply: `export GROUPDOCS_LIC_PATH="path/to/license.lic"` (a file or a URL). It is applied at the first call into the engine, and a licence that cannot be applied raises `LicenseError` from then on instead of falling back to evaluation mode (`GROUPDOCS_LIC_STRICT=0` makes it a warning). `Metered().set_metered_key(public_key, private_key)` activates metered billing; `Metered.get_consumption_quantity()` and `Metered.get_consumption_credit()` report usage.
**Evaluation mode** (no licence): an index takes at most 100 documents (the rest are left out and reported through `error_occurred`), and only the first 10 found documents can be read: `result.get_found_document(10)` raises `LicenseRestrictionException`. A 30-day full license is free: https://purchase.groupdocs.com/temporary-license
**Windows:** applying a licence makes the first call take 15-30 seconds, once per process (the engine's licence check; the same in plain .NET). Reuse the process.
## API Reference
### Index
| Method | Returns | Description |
|---|---|---|
| `Index(index_folder [, IndexSettings])` | | Create or open an on-disk index. Disposable / context manager. |
| `add(path [, IndexingOptions])` | `None` | Index a folder, a file, a list of paths, `Document`s or extracted data. |
| `update([UpdateOptions])` | `None` | Incrementally re-index documents that changed. |
| `optimize([OptimizeOptions])` | `None` | Merge index segments to speed up search. |
| `merge(index)` / `merge(repository)` | `None` | Merge another index into this one. |
| `search(query [, SearchOptions])` | `SearchResult` | Run a text query, a `SearchQuery` or a `SearchImage`. |
| `search_next(chunk_search_token)` | `SearchResult` | Continue a chunked search. |
| `highlight(found_document, highlighter [, HighlightOptions])` | `None` | Render highlights via an `OutputAdapter`. |
| `get_indexed_documents()` | list of `DocumentInfo` | All documents currently in the index. |
| `get_indexed_paths()` | list of `str` | Root paths that were added. |
| `get_document_text(document_info, adapter)` | `None` | Extract a document's indexed text into an adapter. |
| `get_indexing_reports()` / `get_search_reports()` | lists | Indexing and search diagnostics. |
| `delete(paths, UpdateOptions)` | `DeleteResult` | Remove documents from the index. |
| `check_segments(fix)` | `None` | Verify (and optionally repair) index integrity. |
`Index` properties: `index_info` (`IndexInfo`), `index_settings` (`IndexSettings`), `dictionaries` (`DictionaryRepository`), `repository` (`IndexRepository`), `events` (`EventHub`).
### Search options & results
| Type | Notes |
|---|---|
| `SearchOptions` | `use_case_sensitive_search`, `use_synonym_search`, `use_homophone_search`, `use_word_forms_search`, `fuzzy_search`, `spelling_corrector`, `keyboard_layout_corrector`, `max_total_occurrence_count`, `max_occurrence_count_per_term`, `search_document_filter`. |
| `FuzzySearchOptions` | `enabled`, `fuzzy_algorithm`, `only_best_results`, `only_best_results_range`, `consider_transpositions`. |
| `SearchResult` | `document_count`, `occurrence_count`, iteration, `get_found_document(i)`, `truncated`, `warnings`, `search_duration`, `next_chunk_search_token`. |
| `FoundDocument` | `document_info`, `occurrence_count`, `relevance`, `found_fields`, `terms`. |
| `DocumentInfo` | `file_path`, `file_type`, `format_family`, `inner_path`. |
### Indexing & maintenance options
| Type | Notes |
|---|---|
| `IndexSettings` | `in_memory_index`, `use_stop_words`, `index_type`, `document_filter`, `text_storage_settings`, `search_threads`, `logger`, `custom_extractors`, `replace_unpaired_surrogates`, `skip_blended_only_terms`. |
| `IndexingOptions` / `UpdateOptions` | `threads`, `is_async`, `ocr_indexing_options`, `image_indexing_options`, `cancellation`. |
| `MergeOptions` / `OptimizeOptions` / `DeleteOptions` | Index maintenance. |
| `HighlightOptions` | Highlight rendering options. |
### Highlighters & adapters
| Type | Notes |
|---|---|
| `DocumentHighlighter(output_adapter)` | Highlight an entire document. |
| `FragmentHighlighter(OutputFormat)` | Highlight matching fragments; `get_result()` -> fragment containers. |
| `FileOutputAdapter(OutputFormat, path)` / `StringOutputAdapter(OutputFormat)` / `StreamOutputAdapter(OutputFormat, stream)` | Where highlight and text output goes. |
## Key Patterns
- **Properties**: use `snake_case` -- auto-mapped to .NET `PascalCase`. A misspelled property raises `AttributeError` with a "Did you mean" hint instead of being stored
- **Index is a folder**: point `Index(...)` at the same folder to reopen a persisted index; `update()` re-indexes only changes
- **Settings before documents**: dictionaries and word-splitting settings change only while the index is empty
- **One query string, many forms**: keyword, `"phrase"`, `?`/`*` wildcards, `^regex`, `AND/OR/NOT`, and `Field:` facets all parse from one string
- **Options objects**: tweak `SearchOptions` sub-objects in place (e.g. `options.fuzzy_search.enabled = True`) before passing to `search`- **Enums**: UPPER_CASE members (`OutputFormat.HTML`, `DocumentSourceKind.STREAM`); `FormatFamily` is a class of constants (`FormatFamily.ARCHIVE`)
- **Collections**: `for found in result`, `for d in index.get_indexed_documents()` and `len(...)` work
- **Events**: `index.events.<event> += handler(sender, args)`; mind the threads (see Events)
- **Callbacks**: plain Python objects or functions for the engine's interfaces (see Callbacks)
- **Exceptions**: catch `SearchException` / `LicenseRestrictionException` (both subclass the product-root `GroupDocsSearchException`, importable from `groupdocs.search`); `LicenseError` is a licence that could not be applied. `e.message` is the .NET message alone; `str(e)` adds the .NET type and stack trace
- **Params arrays**: a static method takes them as separate arguments or one list (`DocumentFilter.create_file_extension(".txt", ".pdf")`); an instance method or constructor takes one list (`batch.add(path, ["important"])`, `TableDiscreteFunction(1, [Step(5, 2)])`)
## Platform Requirements
| Platform | Requirements |
|---|---|
| Windows | None |
| Linux | glibc 2.27+, `apt install libicu-dev libgdiplus` (ICU is required; libgdiplus for ODP and PPT files). No fonts needed |
| macOS | 13 or newer, `brew install mono-libgdiplus` (for presentations) |
## Troubleshooting
**Only 100 documents indexed, or `LicenseRestrictionException: Only first 10 results can be returned`** -- evaluation mode. Apply a license with `License().set_license(...)` or set `GROUPDOCS_LIC_PATH`; a free 30-day license is at https://purchase.groupdocs.com/temporary-license
**`... cannot be changed because the index ... contains documents`** -- set dictionaries and word-splitting settings before the first `add()` (26.10 behaviour)
**A presentation is not indexed; `error_occurred` reports `The type initializer for 'Gdip' threw an exception` or a `NullReferenceException`** -- install libgdiplus: `sudo apt install libgdiplus` (Linux) / `brew install mono-libgdiplus` (macOS)
**A handler's exception does not stop indexing** -- on an engine thread it is printed and ignored; to skip a document set `args.skip_indexing = True`; to stop an operation use `IndexingOptions.cancellation`**The first call takes 15-30 seconds** -- Windows, with a licence applied: the engine's licence check, once per process
**`DOTNET_SYSTEM_GLOBALIZATION_INVARIANT` errors, or the process aborts with `Couldn't find a valid ICU package`** -- do NOT set this variable. Install ICU: `sudo apt install libicu-dev`**`is not a supported wheel on this platform`** -- pip is older than 20.3 (`python -m pip install --upgrade pip`), or the OS is older than glibc 2.27 / macOS 13
**Still stuck?** Post your question at https://forum.groupdocs.com/c/search/ -- the development team responds directly.
Was this page helpful?
Any additional feedback you'd like to share with us?
Please tell us how we can improve this page.
Thank you for your feedback!
We value your opinion. Your feedback will help us improve our documentation.
On this page
Analyzing your prompt, please hold on...
An error occurred while retrieving the results. Please refresh the page and try again.