{"id":13937080,"url":"https://github.com/search5/solrpy","last_synced_at":"2026-04-02T14:53:01.557Z","repository":{"id":28623387,"uuid":"32142115","full_name":"search5/solrpy","owner":"search5","description":"Automatically exported from code.google.com/p/solrpy","archived":false,"fork":false,"pushed_at":"2026-03-25T10:59:25.000Z","size":659,"stargazers_count":40,"open_issues_count":24,"forks_count":15,"subscribers_count":2,"default_branch":"master","last_synced_at":"2026-03-26T13:49:13.874Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/search5.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-13T08:12:26.000Z","updated_at":"2026-03-25T10:59:26.000Z","dependencies_parsed_at":"2022-09-19T10:10:48.224Z","dependency_job_id":null,"html_url":"https://github.com/search5/solrpy","commit_stats":null,"previous_names":[],"tags_count":56,"template":false,"template_full_name":null,"purl":"pkg:github/search5/solrpy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/search5%2Fsolrpy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/search5%2Fsolrpy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/search5%2Fsolrpy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/search5%2Fsolrpy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/search5","download_url":"https://codeload.github.com/search5/solrpy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/search5%2Fsolrpy/sbom","scorecard":{"id":808189,"data":{"date":"2025-08-11","repo":{"name":"github.com/search5/solrpy","commit":"4cb8c6ea3fbd4f64b78dd300aa637e0de05aeb45"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.1,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":1,"reason":"Found 3/27 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 6 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-23T12:23:01.646Z","repository_id":28623387,"created_at":"2025-08-23T12:23:01.646Z","updated_at":"2025-08-23T12:23:01.646Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31308447,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-07T23:03:15.789Z","updated_at":"2026-04-02T14:53:01.550Z","avatar_url":"https://github.com/search5.png","language":"Python","readme":"# solrpy\n\nsolrpy is a Python client for [Solr], an enterprise search server\nbuilt on top of [Lucene]. solrpy allows you to add documents to a\nSolr instance, and then to perform queries and gather search results\nfrom Solr using Python.\n\n- **Supports Solr 1.2 through 10.x**\n- **Automatic Solr version detection** with runtime feature gating\n- **Python 3.10+** required\n\n## Installation\n\n```bash\npip install solrpy\n```\n\nOr with Poetry:\n\n```bash\npoetry add solrpy\n```\n\n## Overview\n\n```python\nimport solr\n\n# create a connection to a solr server\ns = solr.Solr('http://localhost:8983/solr/mycore')\n\n# the server version is auto-detected\nprint(s.server_version)  # e.g. (9, 4, 1)\n\n# check if the server is reachable\nprint(s.ping())  # True\n\n# add a document to the index\ndoc = {\n    \"id\": 1,\n    \"title\": \"Lucene in Action\",\n    \"author\": [\"Erik Hatcher\", \"Otis Gospodnetić\"],\n}\ns.add(doc, commit=True)\n\n# do a search\nresponse = s.select('title:lucene')\nfor hit in response.results:\n    print(hit['title'])\n```\n\n## Response format\n\nSince v1.0.4, solrpy uses JSON (`wt=json`) by default, matching Solr 7.0+ behavior.\n\nFor legacy XML mode:\n\n```python\ns = solr.Solr('http://localhost:8983/solr/mycore', response_format='xml')\n```\n\nThe `Response` object API is identical regardless of format.\n\n## More powerful queries\n\nOptional parameters for query, faceting, highlighting, and more like this\ncan be passed in as Python parameters to the query method. Convert the\ndot notation (e.g. `facet.field`) to underscore notation (e.g. `facet_field`)\nso that they can be used as parameter names.\n\n```python\nresponse = s.select('title:lucene', facet='true', facet_field='subject')\n```\n\nIf the parameter takes multiple values, pass them in as a list:\n\n```python\nresponse = s.select('title:lucene', facet='true', facet_field=['subject', 'publisher'])\n```\n\n## Version detection\n\nsolrpy automatically detects the connected Solr version and gates features\naccordingly. If a feature requires a newer Solr version than what is\nconnected, a `SolrVersionError` is raised with a clear message.\n\n```python\nimport solr\n\ns = solr.Solr('http://localhost:8983/solr/mycore')\nprint(s.server_version)  # (6, 6, 6)\n```\n\n## Why solrpy over pysolr?\n\n| Feature | solrpy | pysolr |\n|---|:---:|:---:|\n| Basic CRUD | ✅ | ✅ |\n| Async/await | ✅ | — |\n| Streaming Expressions | ✅ | — |\n| KNN / Vector search | ✅ | — |\n| Schema API (full CRUD) | ✅ | — |\n| Pydantic models | ✅ | — |\n| SolrCloud + ZooKeeper | ✅ | ✅ |\n| JSON Facet API | ✅ | — |\n| Structured Field/Sort/Facet builders | ✅ | — |\n| Document extraction (Tika) | ✅ | ✅ |\n| Solr version auto-detection | ✅ | — |\n| Solr 1.2–10.x spanning | ✅ | — |\n| Type hints + py.typed | ✅ | — |\n| Connection pooling (httpx) | ✅ | ✅ |\n| Performance | on par | on par |\n\n### Migrating from pysolr\n\n```python\n# Option 1: Drop-in compatibility wrapper\nfrom solr import PysolrCompat as Solr\n\nconn = Solr('http://localhost:8983/solr/mycore')\nresults = conn.search('title:hello')       # pysolr API\nconn.add([doc1, doc2], commit=True)        # accepts list like pysolr\nconn.delete(id='1', q='old:*')             # q= maps to delete_query\n\n# Option 2: Native solrpy API (recommended)\nfrom solr import Solr\n\nconn = Solr('http://localhost:8983/solr/mycore')\nresults = conn.select('title:hello')       # select instead of search\nconn.add_many([doc1, doc2], commit=True)   # explicit add_many\nconn.delete_query('old:*', commit=True)    # explicit delete_query\n```\n\n## Tests\n\nTests require a running Solr instance. Using Docker:\n\n```bash\ndocker run -d --name solr-dev -p 8983:8983 solr:6.6 solr-precreate core0\npoetry run pytest tests/\n```\n\n## Changelog\n\n### 2.0.8a\n\n- **18 bug fixes**: async event-loop blocking (`time.sleep` → `asyncio.sleep`), date validation logic inverted, highlight `hl_fl` list bug, async timeout leak, PysolrCompat double commit, SolrCloud resource leak, stream pipe mutation, sort false positive, paginator `q` loss, and more\n- Full details in [changelog](https://search5.github.io/solrpy/latest/changelog.html)\n\n### 2.0.8\n\n- **Decimal support**: `decimal.Decimal` values now serialize correctly in both JSON and XML update paths\n- **Security**: all `eval()` usage removed (resolved since 2.x rewrite)\n- **No stray `print()`**: debug print statements fully eliminated from all execution paths\n- **UTF-8 safe**: `setup.py` replaced with Poetry `pyproject.toml`, no encoding issues\n- **`q.op` support**: dotted Solr parameters work via underscore notation (`q_op='AND'` → `q.op=AND`)\n\n### 2.0.7\n\n- **Lazy initialization**: `Solr()` constructor is now instant (~0ms). httpx client and version detection deferred to first use\n- **PysolrCompat**: drop-in compatibility wrapper for pysolr migration (`from solr import PysolrCompat`)\n- **Select performance**: `json.loads(bytes)` instead of `json.loads(string)` eliminates redundant decode\n- Comparison table and migration guide in README\n\n### 2.0.6\n\n- **Async Pydantic models**: `await conn.select('*:*', model=MyDoc)` returns typed results\n- `model=` parameter on `AsyncSolr.select()` — same as sync `SearchHandler`\n\n### 2.0.5\n\n- **Async Streaming Expressions**: `async for doc in await conn.stream(expr):`\n- **serialize_value() bug fix**: `atomic_update()`, `AsyncSolr.add/add_many` now correctly serialize `datetime`, `date`, `bool`\n- **Internal JSON update path**: Solr 4.0+ uses JSON for add/add_many/atomic_update (no user-facing change)\n- `solr_json_default()` encoder handles `datetime`, `date`, `set`, `tuple`\n\n### 2.0.4\n\n- **Unified sync/async API**: `SchemaAPI(conn)` works with both `Solr` and `AsyncSolr`\n- Single class, dual mode — no need for separate `AsyncSchemaAPI` etc.\n- `DualTransport` auto-detects sync vs async connection\n- `_chain()` helper for composing sync values and async coroutines\n- `AsyncSchemaAPI`, `AsyncKNN`, `AsyncMoreLikeThis`, `AsyncSuggest`, `AsyncExtract` kept as backward-compatible aliases\n\n### 2.0.3\n\n- **Async companion classes**: `AsyncSchemaAPI`, `AsyncKNN`, `AsyncMoreLikeThis`, `AsyncSuggest`, `AsyncExtract`\n- Full async support for all companion features\n\n### 2.0.2\n\n- **AsyncSolr**: `async with AsyncSolr(url) as conn: await conn.select('*:*')`\n- **AsyncTransport** for async companion classes\n- Full async: select, add, add_many, delete, commit, get\n\n### 2.0.1\n\n- **Breaking**: `http.client` replaced with `httpx.Client`\n- Automatic **connection pooling** and keep-alive\n- `httpx` is now a required dependency\n- All public API unchanged — drop-in replacement for 1.x\n\n### 1.12.0\n\n- **Streaming Expressions**: Python builder with pipe (`|`) operator — no other non-Java client has this\n- `search`, `merge`, `rollup`, `top`, `unique`, `innerJoin`, etc.\n- Aggregate: `count`, `sum`, `avg`, `min`, `max`\n- `conn.stream(expr)` → iterator of result dicts\n- Pydantic model support via `model=` parameter\n\n### 1.11.0\n\n- **Pydantic response models**: `conn.select('*:*', model=MyDoc)` converts results to Pydantic models\n- `Response.as_models(MyDoc)` for post-hoc conversion\n- `conn.get(id='1', model=MyDoc)` returns `MyDoc | None`\n- `pip install solrpy[pydantic]`\n\n### 1.10.1\n\n- **Field builder**: `Field('price', alias='p')`, `Field.func('sum', 'price', 'tax')`, `Field.transformer('explain')`\n- **Sort builder**: `Sort('price', 'desc')`, `Sort.func('geodist()', 'asc')`\n- **Facet builder**: `Facet.field('category')`, `Facet.range('price', 0, 100, 10)`, `Facet.query()`, `Facet.pivot()`\n- Fully backward compatible — raw strings still work\n\n### 1.10.0\n\n- **SolrCloud**: `SolrCloud(zk, collection)` with ZooKeeper or `SolrCloud.from_urls(urls, collection)` HTTP-only\n- Leader-aware writes, automatic failover, collection aliases\n- `SolrZooKeeper` class for ZooKeeper node discovery\n- `kazoo` optional dependency (`pip install solrpy[cloud]`)\n- Docker Compose for local SolrCloud testing\n\n### 1.9.2\n\n- **Solr 6~10 full compatibility**: `wt=xml` on Solr 7+ (`wt=standard` changed in 7.0)\n- Tested against Solr 6.6, 7.7, 8.11, 9.7, 10.0 — all 0 failures\n- GitHub Actions CI matrix for 5 Solr versions\n- KNN live tests version-gated (skip on \u003c 9.0, efSearchScaleFactor skip on \u003c 10.0)\n- Test isolation: Paginator no longer deletes all documents\n\n### 1.9.1\n\n- **KNN API overhaul**: `search()`, `similarity()`, `hybrid()`, `rerank()` methods\n- Full `{!knn}` parameters: `early_termination`, `seed_query`, `pre_filter`, etc.\n- `{!vectorSimilarity}` threshold search (Solr 9.6+)\n- Hybrid (lexical OR vector) and re-ranking patterns\n\n### 1.9.0\n\n- **KNN / Dense Vector Search**: `KNN(conn)` for `{!knn}` queries (Solr 9.0+)\n\n### 1.8.1\n\n- **HTTP transport abstraction**: `SolrTransport` decouples companion classes from internal `_get`/`_post`\n- SchemaAPI, Suggest, Extract now use `SolrTransport` — prepares for httpx in 2.0.0\n\n### 1.8.0\n\n- **Bearer token auth**: `Solr(url, auth_token='...')`\n- **Custom auth callable**: `Solr(url, auth=my_fn)` for OAuth2 dynamic refresh\n- Priority: `auth` callable \u003e `auth_token` \u003e `http_user/http_pass`\n\n### 1.7.0\n\n- **Grouping / Field Collapsing**: `resp.grouped['field'].groups` for grouped results (Solr 3.3+)\n- `GroupedResult`, `GroupField`, `Group` classes with `groupValue`, `doclist`, `matches`, `ngroups`\n- Works in both JSON and XML modes\n\n### 1.6.0\n\n- **Extract**: `Extract(conn)` wrapper class for Solr Cell (Apache Tika) via `/update/extract` (Solr 1.4+).\n  Index rich documents (PDF, Word, HTML, …) with optional literal field values.\n  `extract_only()` extracts text and metadata without indexing.\n  `from_path()` / `extract_from_path()` open files by filesystem path, MIME type guessed automatically.\n\n```python\nfrom solr import Solr, Extract\n\nconn = Solr('http://localhost:8983/solr/mycore')\nextract = Extract(conn)\n\n# Index a PDF with metadata\nwith open('report.pdf', 'rb') as f:\n    extract(f, content_type='application/pdf',\n            literal_id='report1', literal_title='Annual Report',\n            commit=True)\n\n# Extract text only (no indexing)\ntext, metadata = extract.extract_from_path('report.pdf')\nprint(text[:200])\n\n# Index from path (MIME type auto-detected)\nextract.from_path('document.docx', literal_id='doc1', commit=True)\n```\n\n### 1.5.0\n\n- **Suggest**: `Suggest(conn)` wrapper class for Solr's SuggestComponent (Solr 4.7+).\n  Returns a flat list of suggestion dicts from the `/suggest` handler.\n- **Spellcheck**: `Response.spellcheck` property returns a `SpellcheckResult` object\n  with `.collation` and `.suggestions` accessors. Works in both JSON and XML modes (Solr 1.4+).\n\n```python\nfrom solr import Solr, Suggest\n\nconn = Solr('http://localhost:8983/solr/mycore')\n\n# Suggest\nsuggest = Suggest(conn)\nresults = suggest('que', dictionary='mySuggester', count=5)\nfor s in results:\n    print(s['term'], s['weight'])\n\n# Spellcheck\nresp = conn.select('misspeled query', spellcheck='true', spellcheck_collate='true')\nif resp.spellcheck and not resp.spellcheck.correctly_spelled:\n    print('Did you mean:', resp.spellcheck.collation)\n```\n\n### 1.4.2\n\n- New `MoreLikeThis(conn)` wrapper class — no need to know `/mlt` path\n\n### 1.4.1\n\n- **Breaking**: `conn.schema` and `conn.mlt` removed from auto-initialization\n- Use `SchemaAPI(conn)` and `SearchHandler(conn, '/mlt')` explicitly\n- Keeps `Solr` class lightweight; optional features created on demand\n\n### 1.4.0\n\n- **Schema API**: `conn.schema.fields()`, `add_field()`, `replace_field()`, `delete_field()`, copy fields, dynamic fields, field types (Solr 4.2+)\n\n### 1.3.0\n\n- **JSON Facet API**: `json_facet` parameter for advanced faceting (Solr 5.0+)\n\n### 1.2.0\n\n- **Cursor pagination**: `resp.cursor_next()` and `conn.iter_cursor()` for deep pagination (Solr 4.7+)\n\n### 1.1.0\n\n- **Soft Commit**: `conn.commit(soft_commit=True)` (Solr 4.0+)\n- **Atomic Update**: `conn.atomic_update(doc)` with `set`/`add`/`remove`/`inc` modifiers (Solr 4.0+)\n- **Real-time Get**: `conn.get(id='doc1')` via `/get` handler (Solr 4.0+)\n- **MoreLikeThis**: `conn.mlt` handler for similar document search (Solr 4.0+)\n\n### 1.0.9\n\n- Per-request timeout override: `conn.select('*:*', timeout=5)`\n\n### 1.0.8\n\n- Exponential backoff on connection retries with configurable `retry_delay`\n- Each retry logged at WARNING level\n\n### 1.0.7\n\n- **Breaking**: `EmptyPage` now inherits `ValueError` (was `SolrException`)\n- New `PageNotAnInteger` exception (inherits `TypeError`)\n- Paginator module no longer depends on `SolrException`\n\n### 1.0.6\n\n- URL validation: warns if URL path doesn't contain `/solr` (Solr 10.0+ preparation)\n\n### 1.0.5\n\n- **Breaking**: Removed `SolrConnection` class. Use `Solr` instead\n- Migration: `add(**fields)` → `add(dict)`, `query()` → `select()`, `raw_query()` → `select.raw()`\n\n### 1.0.4\n\n- **Breaking**: Default `response_format` changed from `'xml'` to `'json'`\n- Pass `response_format='xml'` explicitly for legacy XML behavior\n\n### 1.0.3\n\n- Added `response_format` constructor option (`'xml'` or `'json'`)\n- Split `solr/core.py` into `exceptions.py`, `utils.py`, `response.py`, `parsers.py`\n- All existing imports continue to work (re-exported via `__init__.py`)\n\n### 1.0.2\n\n- `mypy --strict` passes with zero errors on `solr/` package\n- Added type hints to all internal classes (`ResponseContentHandler`, `Node`, `Results`, `UTC`)\n- Fixed `endElement` variable shadowing for type safety\n\n### 1.0.1\n\n- Added type hints to all public methods in `solr/core.py` and `solr/paginator.py`\n- Added `solr/py.typed` marker file for PEP 561 compatibility\n- Added `mypy` to dev dependencies\n- mypy passes with zero errors on `solr/` package\n\n### 0.9.11\n\n- Added JSON response parser (`parse_json_response`)\n- Added `Solr.ping()` convenience method\n- Added `always_commit` constructor option for auto-commit behavior\n- Added gzip response support (`Accept-Encoding: gzip`)\n\n### 0.9.10\n\n- Added pyproject.toml metadata (authors, maintainers, classifiers, keywords)\n- Added Sphinx documentation (quickstart, API reference, version detection, changelog)\n- Rewrote README.md with current API examples and Docker test instructions\n- Updated CLAUDE.md development guidelines\n\n### 0.9.9\n\n- Removed deprecated `encoder`/`decoder` attributes and `codecs` import\n- Fixed `commit(_optimize=True)` to correctly issue `\u003coptimize/\u003e` command\n- Added test coverage for `\u003cdouble\u003e` XML type parsing\n- Added test coverage for named `\u003cresult\u003e` tag handling\n- Added Solr version auto-detection (`server_version`)\n- Added `SolrVersionError` exception and `requires_version` decorator\n- Removed all Python 2 compatibility code (Python 3.10+ only)\n- Migrated from setuptools to Poetry\n- Bumped version to 0.9.9\n\n## License\n\n[Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\n[Solr]: https://solr.apache.org/\n[Lucene]: https://lucene.apache.org/\n","funding_links":[],"categories":["Python","Awesome Python"],"sub_categories":["Search"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsearch5%2Fsolrpy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsearch5%2Fsolrpy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsearch5%2Fsolrpy/lists"}