GroupDocs.Search for Python via .NET 26.10 Release Notes

GroupDocs.Search for Python via .NET 26.10 is the first release of the product.

It brings the GroupDocs.Search 26.10 engine to Python: build a full-text index over folders of PDF, Word, Excel, PowerPoint, email, eBook, archive and other documents, 80+ formats in all, keep it on disk and update it as files change, then search it with keyword, phrase, wildcard, regular-expression, fuzzy, boolean, faceted and date-range queries, and highlight what was found.

The wheels embed the .NET 10.0.14 runtime and run on Python 3.5 – 3.14 on Windows x64, Linux x64 (glibc 2.27 or newer) and macOS 13 or newer on Intel and Apple Silicon. Currently packages are on releases.groupdocs.com now and come to PyPI soon.

Full List of Changes in This Release

KeyCategorySummary
SEARCHPYTHONNET‑40FeatureInitial release of GroupDocs.Search for Python via .NET

Major Features

Index documents and search them

Index(folder) creates an index in a folder, or opens the index already there. add() indexes a folder, a file, a list of paths, streams or extracted data, and search() takes a query in the GroupDocs.Search query language: keywords, phrases in quotes, wildcards (? for one character, ?(1~5) for one to five), regular expressions after ^, AND, OR and NOT, field queries such as content: Bern or filename: einstein, and date and number ranges. SearchQuery builds the same queries as objects.

from groupdocs.search import Index

with Index("./index") as index:              # creates the index folder, or opens it
    index.add("./documents")                 # indexes every supported file in the folder
    for query in ["relativity", '"theory of relativity"', "relativ?ty",
                  "^relativ.*", "Einstein AND NOT Newton", "content: Bern"]:
        result = index.search(query)
        print("%-26s %d documents, %d occurrences"
              % (query, result.document_count, result.occurrence_count))

Fuzzy search tolerates typing and OCR errors. Synonym, homophone and word-form search widen a query through the built-in English dictionaries, which index.dictionaries lets you edit, and spelling and keyboard-layout correction are search options too.

from groupdocs.search import Index
from groupdocs.search.options import SearchOptions, SimilarityLevel

with Index("./index") as index:
    index.add("./documents")

    fuzzy = SearchOptions()
    fuzzy.fuzzy_search.enabled = True
    fuzzy.fuzzy_search.fuzzy_algorithm = SimilarityLevel(0.8)   # 0.0 (loose) .. 1.0 (strict)
    print("relativty:", index.search("relativty").document_count,
          "->", index.search("relativty", fuzzy).document_count)

    synonyms = SearchOptions()
    synonyms.use_synonym_search = True
    print("picture:", index.search("picture").document_count,
          "->", index.search("picture", synonyms).document_count)

    word_forms = SearchOptions()
    word_forms.use_word_forms_search = True
    print("letter:", index.search("letter").document_count,
          "->", index.search("letter", word_forms).document_count)

Highlight what was found

DocumentHighlighter writes a whole document with the found terms marked, and FragmentHighlighter only the fragments around them, as HTML or plain text, into a file, a string or a binary file object.

from groupdocs.search import Index
from groupdocs.search.common import FileOutputAdapter
from groupdocs.search.highlighters import DocumentHighlighter
from groupdocs.search.options import OutputFormat

with Index("./index") as index:
    index.add("./documents")
    result = index.search("relativity")
    if result.document_count > 0:
        found = result.get_found_document(0)
        adapter = FileOutputAdapter(OutputFormat.HTML, "./highlighted.html")
        index.highlight(found, DocumentHighlighter(adapter))
        print(found.document_info.file_path, "-> highlighted.html")

.NET events as Python handlers

Every event of the index is a Python event: subscribe with += and unsubscribe with -=. A handler can skip a document, tag it with attributes or supply its password, and follows the progress, errors and phases of indexing and search.

from groupdocs.search import Index

skipped = []

def on_file_indexing(sender, args):
    if args.document_full_path.endswith(".zip"):
        args.skip_indexing = True            # leave archives out of the index
        skipped.append(args.document_full_path)

with Index("./index") as index:
    index.events.file_indexing += on_file_indexing
    index.add("./documents")
    index.events.file_indexing -= on_file_indexing
    print(len(index.get_indexed_documents()), "documents indexed,", len(skipped), "archives skipped")

A handler runs on the thread that raises the event. With IndexingOptions.threads above 1, or with is_async, that is an engine thread: an exception a handler raises there is printed to stderr and ignored.

Python objects for the engine’s extension points

Custom text extractors for formats of your own, loggers, word splitters, word-form providers, document loaders and OCR connectors are plain Python objects or functions, which the engine calls through its interfaces.

from groupdocs.search import Index, IndexSettings

class LogExtractor(object):
    """Text of .log files, which the engine does not read on its own."""
    extensions = [".log"]

    def get_fields(self, source):             # a file path, or the engine's stream
        if isinstance(source, str):
            with open(source, encoding="utf-8") as f:
                text = f.read()
        else:
            text = source.read().decode("utf-8")
        return [("Content", text)]

settings = IndexSettings()
settings.custom_extractors.add(LogExtractor())
with Index("./log-index", settings) as index:
    index.add("./logs")
    print(index.search("quota").document_count, "log file(s) mention a quota")

Keep an index up to date

An index is a folder: build it once and reopen it in a later run. update() re-indexes only the documents that changed since the last add() or update(), optimize() merges the index segments, and delete() removes documents by path.

from groupdocs.search import Index

with Index("./index") as index:
    index.add("./documents")
    # ... files in ./documents are added, changed or removed ...
    index.update()        # re-indexes only what changed
    index.optimize()      # merges index segments so searches stay fast
    print(len(index.get_indexed_documents()), "documents in the index")

A license from the environment

Set GROUPDOCS_LIC_PATH to the path or URL of a license file, and the license is applied at the first call into the engine; License().set_license(path) does the same in code. A license that cannot be applied raises LicenseError from that first call on, instead of falling back to evaluation mode. Without a license, an index takes at most 100 documents and only the first 10 found documents of a search can be read.

export GROUPDOCS_LIC_PATH="/path/to/GroupDocs.Search.lic"

API Changes

This is the first release, so there is nothing to upgrade from. The API follows GroupDocs.Search for .NET 26.10: classes keep their .NET names, while methods, properties and parameters are written in snake_case (Index.Search is index.search, SearchResult.DocumentCount is result.document_count) and enum members in upper case (OutputFormat.HTML). Assigning an attribute that a class does not have raises AttributeError with a “Did you mean” hint, instead of creating a Python-only attribute the engine never sees.

Modules

  • groupdocs.search: Index, IndexSettings, IndexRepository, IndexUpdater, SearchQuery, Extractor, License, Metered, GroupDocsSearchException, LicenseError, CancellationTokenSource.
  • groupdocs.search.options: IndexingOptions, SearchOptions, FuzzySearchOptions, SimilarityLevel, UpdateOptions, HighlightOptions, OutputFormat, SeparateProcessOptions, ImageSearchOptions.
  • groupdocs.search.results: SearchResult, FoundDocument, FoundDocumentField, DocumentInfo, FileType, FormatFamily.
  • groupdocs.search.highlighters: DocumentHighlighter, FragmentHighlighter.
  • groupdocs.search.common: FileOutputAdapter, StringOutputAdapter, StreamOutputAdapter, Document, DocumentField, ExtractedData, FileLogger, AttributeChangeBatch.
  • groupdocs.search.dictionaries: SynonymDictionary, HomophoneDictionary, StopWordDictionary, SpellingCorrector, AliasDictionary, CharacterReplacementPair.
  • groupdocs.search.events: EventHub, FileIndexingEventArgs, IndexErrorEventArgs, OperationProgressEventArgs, PasswordRequiredEventArgs.
  • groupdocs.search.exceptions: SearchException, LicenseRestrictionException.
  • groupdocs.search.scaling: SearchNetworkNode, Indexer, Searcher.
  • groupdocs.search.scaling.configuring: Configuration, Configurator, TcpSettings.

Known Issues

  • On Linux, without ICU the process aborts on the first engine call with Couldn't find a valid ICU package, and no Python exception is raised. Install ICU (apt-get install libicu-dev, any version) and do not set DOTNET_SYSTEM_GLOBALIZATION_INVARIANT.
  • On Linux and macOS, presentations need libgdiplus. Without it, ODP and PPT files are left out of the index: add() reports them through error_occurred, with The type initializer for 'Gdip' threw an exception for ODP and a NullReferenceException for PPT, and indexes the rest. Install it with apt-get install libgdiplus or brew install mono-libgdiplus.
  • With pip older than 20.3, or on Linux older than glibc 2.27 or macOS older than 13, pip refuses the wheel with is not a supported wheel on this platform. Upgrade pip with python -m pip install --upgrade pip.

Requirements

  • Python 3.5 – 3.14 (python_requires = ">=3.5,<3.15") and pip 20.3 or newer, 64-bit
  • Windows x64, Linux x64 with glibc 2.27 or newer, macOS 13 or newer on Intel or Apple Silicon. On an Intel Mac, a Python built against a pre-11 macOS SDK reports the system as macOS 10.16 — use pip 24.1 or newer there, or set SYSTEM_VERSION_COMPAT=0 when running pip
  • Windows: no additional packages
  • Linux: ICU and libgdiplus — apt-get install libicu-dev libgdiplus. ICU is required for any document. libgdiplus serves presentations: without it, ODP and PPT files are left out of the index, and every other format indexes the same. No fonts are needed
  • macOS: brew install mono-libgdiplus, for presentations as on Linux

Wheel Distribution

26.10 is published on releases.groupdocs.com first, and on PyPI once PyPI raises the package’s file-size limit. Until then, pip install groupdocs-search-net finds no matching distribution. Download the wheel for your platform and install the file, keeping its name, from which pip reads the platform:

PlatformDownloadWheel
Windows x64Windowsgroupdocs_search_net-26.10.0-py3-none-win_amd64.whl
Linux x64Linuxgroupdocs_search_net-26.10.0-py3-none-manylinux_2_27_x86_64.whl
macOS IntelmacOS Intelgroupdocs_search_net-26.10.0-py3-none-macosx_13_0_x86_64.whl
macOS Apple SiliconmacOS Apple Silicongroupdocs_search_net-26.10.0-py3-none-macosx_13_0_arm64.whl
pip install groupdocs_search_net-26.10.0-py3-none-win_amd64.whl

Additional Resources

Feedback

Questions and issues are welcome on the GroupDocs.Search forum.