xpath-kit


Namexpath-kit JSON
Version 0.3.5 PyPI version JSON
download
home_pageNone
SummaryA toolkit for convenient and expressive XPath operations based on lxml.
upload_time2025-09-03 10:50:46
maintainerNone
docs_urlNone
authorNone
requires_python>=3.11
licenseNone
keywords xpath lxml xml html parser
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # xpath-kit

[![PyPI Version](https://img.shields.io/pypi/v/xpath-kit.svg)](https://pypi.org/project/xpath-kit/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python Versions](https://img.shields.io/pypi/pyversions/xpath-kit.svg)](https://pypi.org/project/xpath-kit/)

**xpath-kit** is a powerful Python library that provides a fluent, object-oriented, and Pythonic interface for building and executing XPath queries on top of `lxml`. It transforms complex, error-prone XPath string composition into a highly readable and maintainable chain of objects and methods.

Say goodbye to messy, hard-to-read XPath strings:

`div[@id="main" and contains(@class, "content")]/ul/li[position()=1]`

And say hello to a more intuitive and IDE-friendly way of writing queries:

`E.div[(A.id == "main") & A.class_.contains("content")] / E.ul / E.li[1]`

---

## ✨ Features

-   **🐍 Fluent & Pythonic Interface**: Chain methods and operators (`/`, `//`, `[]`, `&`, `|`, `==`, `>`) to build complex XPath expressions naturally using familiar Python logic.
-   **💡 Smart Builders**: Use `E` (elements), `A` (attributes), and `F` (functions) for a highly readable syntax with excellent IDE autocompletion support.
-   **📖 Superb Readability & Maintainability**: Complex queries become self-documenting. It's easier to understand, debug, and modify your selectors.
-   **💪 Powerful Predicate Logic**: Easily create sophisticated predicates for attributes, text, and functions. Gracefully handle multi-class selections with `any()`, `all()`, and `none()`.
-   **🔩 Convenient DOM Manipulation**: The result objects are powerful wrappers around `lxml` elements, allowing for easy DOM traversal and manipulation (e.g., `append`, `remove`, `parent`, `next_sibling`).
-   **🔒 Fully Type-Hinted**: The entire library is fully type-hinted for an unmatched developer experience and static analysis with modern IDEs.
-   **⚙️ HTML & XML Support**: Seamlessly parse both document types with `html()` and `xml()` entry points.

---

## 🚀 Installation

Install `xpath-kit` from PyPI using pip:

```bash
pip install xpath-kit
```

The library requires `lxml` as a dependency, which will be installed automatically.

---

## 🏁 Quick Start

Here's a simple example of how to use `xpath-kit` to parse a piece of HTML and extract information.

```python
from xpathkit import html, E, A, F

html_content = """
<html>
  <body>
    <div id="main">
      <h2>Article Title</h2>
      <p>This is the first paragraph.</p>
      <ul class="item-list">
        <li class="item active">Item 1</li>
        <li class="item">Item 2</li>
        <li class="item disabled">Item 3</li>
      </ul>
    </div>
  </body>
</html>
"""

# 1. Parse the HTML content
root = html(html_content)

# 2. Build a query to find the <li> element with both "item" and "active" classes
# XPath: .//ul[contains(@class, "item-list")]/li[contains(@class, "item") and contains(@class, "active")]
query = E.ul[A.class_.contains("item-list")] / E.li[A.class_.all("item", "active")]

# 3. Execute the query and get a single element
active_item = root.descendant(query)

# Print its content and attributes
print(f"Tag: {active_item.tag}")
print(f"Text: {active_item.string()}")
print(f"Class attribute: {active_item['class']}")

# --- Output ---
# Tag: li
# Text: Item 1
# Class attribute: item active

# 4. Build a more complex query: find all <li> elements whose class does NOT contain 'disabled'
# XPath: .//li[not(contains(@class, "disabled"))]
query_enabled = E.li[F.not_(A.class_.contains("disabled"))]

# 5. Execute the query and process the list of results
enabled_items = root.descendants(query_enabled)
item_texts = enabled_items.map(lambda item: item.string())
print(f"\nEnabled items: {item_texts}")

# --- Output ---
# Enabled items: ['Item 1', 'Item 2']

```

---

## 📚 Core Concepts

### 1. Parsing Entrypoints

Use the `html()` or `xml()` functions to start. They accept a string, bytes, or a file path.

```python
from xpathkit import html, xml

# Parse an HTML string
root_html = html("<div><p>Hello</p></div>")

# Parse an XML file
root_xml = xml(path="data.xml")
```

### 2. The Smart Builders (E, A, F)

These are the heart of `xpath-kit`, making expression building effortless.

-   **`E` (Element)**: Builds element nodes. E.g., `E.div`, `E.a`, or custom tags `E["my-tag"]`.
-   **`A` (Attribute)**: Builds attribute nodes within predicates. E.g., `A.id`, `A.href`, or custom attributes `A["data-id"]`.
-   **`F` (Function)**: Builds XPath functions. E.g., `F.contains()`, `F.not_()`, `F.position()`, or any custom function: `F["name"](arg1, ...)`.

*Note*: Since `class` and `for` are reserved keywords in Python, use a trailing underscore: `A.class_` and `A.for_`.

### 3. Path Selection (`/` and `//`)

Use the division operators to define relationships between elements.

-   `/`: Selects a direct child.
-   `//`: Selects a descendant at any level.

```python
# Selects a <p> that is a direct child of a <div>
# XPath: div/p
query_child = E.div / E.p

# Selects an <a> that is a descendant of the <body>
# XPath: body//a
query_descendant = E.body // E.a
```

You can also use a string directly after an element for simple cases:

```python
# Equivalent to E.div / E.span
query = E.div / "span"
```

This is convenient for simple queries without predicates or attributes.

### 4. Predicates (`[]`)

Use square brackets `[]` on an element to add filtering conditions. This is where `xpath-kit` truly shines.

#### Attribute Predicates with `A`

```python
# Find a div with id="main"
# XPath: //div[@id="main"]
query = E.div[A.id == "main"]

# Find an <a> that has an href attribute
# XPath: //a[@href]
query_has_href = E.a[A.href]

# Find an <li> whose class contains "item" but NOT "disabled"
# XPath: //li[contains(@class,"item") and not(contains(@class,"disabled"))]
query = E.li[A.class_.contains("item") & F.not_(A.class_.contains("disabled"))]
```

#### Text/Value Predicates

To query against the string value of a node (`.`), import the `dot` class.

```python
from xpathkit import dot

# Find an <h1> whose text is exactly "Welcome"
# XPath: //h1[.="Welcome"]
query = E.h1[dot() == "Welcome"]

# Find a <p> whose text contains the word "paragraph"
# XPath: //p[contains(., "paragraph")]
query_contains = E.p[dot().contains("paragraph")]
```

#### Functional Predicates with `F`

Use `F` to call any standard XPath function inside a predicate.

```python
# Select the first list item
# XPath: //li[position()=1]
query_first = E.li[F.position() == 1]

# Select the last list item
# XPath: //li[last()]
query_last = E.li[F.last()]
```

#### Combining Predicates with `&` and `|`

-   `&`: Logical `and`
-   `|`: Logical `or`

```python
# Find an <a> with href="/home" AND a target attribute
# XPath: //a[@href="/home" and @target]
query_and = E.a[(A.href == "/home") & A.target]

# Find a <div> with id="sidebar" OR class="nav"
# XPath: //div[@id="sidebar" or contains(@class,"nav")]
query_or = E.div[(A.id == "sidebar") | A.class_.contains("nav")]
```
**Important:** Due to Python's operator precedence, it's highly recommended to wrap combined conditions in parentheses `()`.

#### Positional Predicates

Use integers (1-based) or negative integers (from the end) directly.

```python
# Select the second <li>
# XPath: //li[2]
query = E.li[2]

# Select the last <li> (equivalent to F.last())
# XPath: //li[last()]
query_last = E.li[-1]
```

### 5. Working with Results

-   `.child()`/`.descendant()` return a single `XPathElement`.
-   `.children()`/`.descendants()` return an `Union[XPathElementList, str, float, bool, List[str]]`.

#### `XPathElement` (Single Result)

-   `.tag`: The element's tag name (e.g., `'div'`).
-   `.attr`: A dictionary of all attributes.
-   `element['name']`: Access an attribute directly.
-   `.string()`: Get the concatenated text of the element and all its children (`string(.)`).
-   `.text()`: Get a list of only the element's direct text nodes (`./text()`).
-   `.parent()`: Get the parent element.
-   `.next_sibling()` / `.prev_sibling()`: Get adjacent sibling elements.
-   `.xpath(query)`: Execute a raw string or a constructed query within the context of this element.

#### `XPathElementList` (Multiple Results)

-   `.one()`: Ensures the list contains exactly one element and returns it; otherwise, raises an error.
-   `.first()` / `.last()`: Get the first or last element; raises an error if the list is empty.
-   `len(element_list)`: Get the number of elements.
-   `.filter(func)`: Filter the list based on a function.
-   `.map(func)`: Apply a function to each element and return a list of the results.
-   Can be iterated over directly: `for e in my_list: ...`
-   Supports slicing and indexing: `my_list[0]`, `my_list[-1]`

### 6. DOM Manipulation

Modify the document tree with ease.

```python
from xpathkit import XPathElement, E, A

# Assuming 'root' is a parsed XPathElement
# Find the <ul> element
ul = root.descendant(E.ul)

# Create and append a new <li>
new_li = XPathElement.create("li", attr={"class": "new-item"}, text="Item 4")
ul.append(new_li)

# Remove an element
item_to_remove = ul.child(E.li[A.class_.contains("disabled")])
if item_to_remove:
    ul.remove(item_to_remove)

# Print the modified HTML
print(root.tostring())
```

---

## 📄 License

This project is licensed under the MIT License. See the `LICENSE` file for details.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "xpath-kit",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": "xpath, lxml, xml, html, parser",
    "author": null,
    "author_email": "Kabxx <2201174299@qq.com>",
    "download_url": "https://files.pythonhosted.org/packages/c2/ca/dfca1edd91bec3d8fbcbd1289463f6f1d42cae32f6619ef97914d754c451/xpath_kit-0.3.5.tar.gz",
    "platform": null,
    "description": "# xpath-kit\r\n\r\n[![PyPI Version](https://img.shields.io/pypi/v/xpath-kit.svg)](https://pypi.org/project/xpath-kit/)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\r\n[![Python Versions](https://img.shields.io/pypi/pyversions/xpath-kit.svg)](https://pypi.org/project/xpath-kit/)\r\n\r\n**xpath-kit** is a powerful Python library that provides a fluent, object-oriented, and Pythonic interface for building and executing XPath queries on top of `lxml`. It transforms complex, error-prone XPath string composition into a highly readable and maintainable chain of objects and methods.\r\n\r\nSay goodbye to messy, hard-to-read XPath strings:\r\n\r\n`div[@id=\"main\" and contains(@class, \"content\")]/ul/li[position()=1]`\r\n\r\nAnd say hello to a more intuitive and IDE-friendly way of writing queries:\r\n\r\n`E.div[(A.id == \"main\") & A.class_.contains(\"content\")] / E.ul / E.li[1]`\r\n\r\n---\r\n\r\n## \u2728 Features\r\n\r\n-   **\ud83d\udc0d Fluent & Pythonic Interface**: Chain methods and operators (`/`, `//`, `[]`, `&`, `|`, `==`, `>`) to build complex XPath expressions naturally using familiar Python logic.\r\n-   **\ud83d\udca1 Smart Builders**: Use `E` (elements), `A` (attributes), and `F` (functions) for a highly readable syntax with excellent IDE autocompletion support.\r\n-   **\ud83d\udcd6 Superb Readability & Maintainability**: Complex queries become self-documenting. It's easier to understand, debug, and modify your selectors.\r\n-   **\ud83d\udcaa Powerful Predicate Logic**: Easily create sophisticated predicates for attributes, text, and functions. Gracefully handle multi-class selections with `any()`, `all()`, and `none()`.\r\n-   **\ud83d\udd29 Convenient DOM Manipulation**: The result objects are powerful wrappers around `lxml` elements, allowing for easy DOM traversal and manipulation (e.g., `append`, `remove`, `parent`, `next_sibling`).\r\n-   **\ud83d\udd12 Fully Type-Hinted**: The entire library is fully type-hinted for an unmatched developer experience and static analysis with modern IDEs.\r\n-   **\u2699\ufe0f HTML & XML Support**: Seamlessly parse both document types with `html()` and `xml()` entry points.\r\n\r\n---\r\n\r\n## \ud83d\ude80 Installation\r\n\r\nInstall `xpath-kit` from PyPI using pip:\r\n\r\n```bash\r\npip install xpath-kit\r\n```\r\n\r\nThe library requires `lxml` as a dependency, which will be installed automatically.\r\n\r\n---\r\n\r\n## \ud83c\udfc1 Quick Start\r\n\r\nHere's a simple example of how to use `xpath-kit` to parse a piece of HTML and extract information.\r\n\r\n```python\r\nfrom xpathkit import html, E, A, F\r\n\r\nhtml_content = \"\"\"\r\n<html>\r\n  <body>\r\n    <div id=\"main\">\r\n      <h2>Article Title</h2>\r\n      <p>This is the first paragraph.</p>\r\n      <ul class=\"item-list\">\r\n        <li class=\"item active\">Item 1</li>\r\n        <li class=\"item\">Item 2</li>\r\n        <li class=\"item disabled\">Item 3</li>\r\n      </ul>\r\n    </div>\r\n  </body>\r\n</html>\r\n\"\"\"\r\n\r\n# 1. Parse the HTML content\r\nroot = html(html_content)\r\n\r\n# 2. Build a query to find the <li> element with both \"item\" and \"active\" classes\r\n# XPath: .//ul[contains(@class, \"item-list\")]/li[contains(@class, \"item\") and contains(@class, \"active\")]\r\nquery = E.ul[A.class_.contains(\"item-list\")] / E.li[A.class_.all(\"item\", \"active\")]\r\n\r\n# 3. Execute the query and get a single element\r\nactive_item = root.descendant(query)\r\n\r\n# Print its content and attributes\r\nprint(f\"Tag: {active_item.tag}\")\r\nprint(f\"Text: {active_item.string()}\")\r\nprint(f\"Class attribute: {active_item['class']}\")\r\n\r\n# --- Output ---\r\n# Tag: li\r\n# Text: Item 1\r\n# Class attribute: item active\r\n\r\n# 4. Build a more complex query: find all <li> elements whose class does NOT contain 'disabled'\r\n# XPath: .//li[not(contains(@class, \"disabled\"))]\r\nquery_enabled = E.li[F.not_(A.class_.contains(\"disabled\"))]\r\n\r\n# 5. Execute the query and process the list of results\r\nenabled_items = root.descendants(query_enabled)\r\nitem_texts = enabled_items.map(lambda item: item.string())\r\nprint(f\"\\nEnabled items: {item_texts}\")\r\n\r\n# --- Output ---\r\n# Enabled items: ['Item 1', 'Item 2']\r\n\r\n```\r\n\r\n---\r\n\r\n## \ud83d\udcda Core Concepts\r\n\r\n### 1. Parsing Entrypoints\r\n\r\nUse the `html()` or `xml()` functions to start. They accept a string, bytes, or a file path.\r\n\r\n```python\r\nfrom xpathkit import html, xml\r\n\r\n# Parse an HTML string\r\nroot_html = html(\"<div><p>Hello</p></div>\")\r\n\r\n# Parse an XML file\r\nroot_xml = xml(path=\"data.xml\")\r\n```\r\n\r\n### 2. The Smart Builders (E, A, F)\r\n\r\nThese are the heart of `xpath-kit`, making expression building effortless.\r\n\r\n-   **`E` (Element)**: Builds element nodes. E.g., `E.div`, `E.a`, or custom tags `E[\"my-tag\"]`.\r\n-   **`A` (Attribute)**: Builds attribute nodes within predicates. E.g., `A.id`, `A.href`, or custom attributes `A[\"data-id\"]`.\r\n-   **`F` (Function)**: Builds XPath functions. E.g., `F.contains()`, `F.not_()`, `F.position()`, or any custom function: `F[\"name\"](arg1, ...)`.\r\n\r\n*Note*: Since `class` and `for` are reserved keywords in Python, use a trailing underscore: `A.class_` and `A.for_`.\r\n\r\n### 3. Path Selection (`/` and `//`)\r\n\r\nUse the division operators to define relationships between elements.\r\n\r\n-   `/`: Selects a direct child.\r\n-   `//`: Selects a descendant at any level.\r\n\r\n```python\r\n# Selects a <p> that is a direct child of a <div>\r\n# XPath: div/p\r\nquery_child = E.div / E.p\r\n\r\n# Selects an <a> that is a descendant of the <body>\r\n# XPath: body//a\r\nquery_descendant = E.body // E.a\r\n```\r\n\r\nYou can also use a string directly after an element for simple cases:\r\n\r\n```python\r\n# Equivalent to E.div / E.span\r\nquery = E.div / \"span\"\r\n```\r\n\r\nThis is convenient for simple queries without predicates or attributes.\r\n\r\n### 4. Predicates (`[]`)\r\n\r\nUse square brackets `[]` on an element to add filtering conditions. This is where `xpath-kit` truly shines.\r\n\r\n#### Attribute Predicates with `A`\r\n\r\n```python\r\n# Find a div with id=\"main\"\r\n# XPath: //div[@id=\"main\"]\r\nquery = E.div[A.id == \"main\"]\r\n\r\n# Find an <a> that has an href attribute\r\n# XPath: //a[@href]\r\nquery_has_href = E.a[A.href]\r\n\r\n# Find an <li> whose class contains \"item\" but NOT \"disabled\"\r\n# XPath: //li[contains(@class,\"item\") and not(contains(@class,\"disabled\"))]\r\nquery = E.li[A.class_.contains(\"item\") & F.not_(A.class_.contains(\"disabled\"))]\r\n```\r\n\r\n#### Text/Value Predicates\r\n\r\nTo query against the string value of a node (`.`), import the `dot` class.\r\n\r\n```python\r\nfrom xpathkit import dot\r\n\r\n# Find an <h1> whose text is exactly \"Welcome\"\r\n# XPath: //h1[.=\"Welcome\"]\r\nquery = E.h1[dot() == \"Welcome\"]\r\n\r\n# Find a <p> whose text contains the word \"paragraph\"\r\n# XPath: //p[contains(., \"paragraph\")]\r\nquery_contains = E.p[dot().contains(\"paragraph\")]\r\n```\r\n\r\n#### Functional Predicates with `F`\r\n\r\nUse `F` to call any standard XPath function inside a predicate.\r\n\r\n```python\r\n# Select the first list item\r\n# XPath: //li[position()=1]\r\nquery_first = E.li[F.position() == 1]\r\n\r\n# Select the last list item\r\n# XPath: //li[last()]\r\nquery_last = E.li[F.last()]\r\n```\r\n\r\n#### Combining Predicates with `&` and `|`\r\n\r\n-   `&`: Logical `and`\r\n-   `|`: Logical `or`\r\n\r\n```python\r\n# Find an <a> with href=\"/home\" AND a target attribute\r\n# XPath: //a[@href=\"/home\" and @target]\r\nquery_and = E.a[(A.href == \"/home\") & A.target]\r\n\r\n# Find a <div> with id=\"sidebar\" OR class=\"nav\"\r\n# XPath: //div[@id=\"sidebar\" or contains(@class,\"nav\")]\r\nquery_or = E.div[(A.id == \"sidebar\") | A.class_.contains(\"nav\")]\r\n```\r\n**Important:** Due to Python's operator precedence, it's highly recommended to wrap combined conditions in parentheses `()`.\r\n\r\n#### Positional Predicates\r\n\r\nUse integers (1-based) or negative integers (from the end) directly.\r\n\r\n```python\r\n# Select the second <li>\r\n# XPath: //li[2]\r\nquery = E.li[2]\r\n\r\n# Select the last <li> (equivalent to F.last())\r\n# XPath: //li[last()]\r\nquery_last = E.li[-1]\r\n```\r\n\r\n### 5. Working with Results\r\n\r\n-   `.child()`/`.descendant()` return a single `XPathElement`.\r\n-   `.children()`/`.descendants()` return an `Union[XPathElementList, str, float, bool, List[str]]`.\r\n\r\n#### `XPathElement` (Single Result)\r\n\r\n-   `.tag`: The element's tag name (e.g., `'div'`).\r\n-   `.attr`: A dictionary of all attributes.\r\n-   `element['name']`: Access an attribute directly.\r\n-   `.string()`: Get the concatenated text of the element and all its children (`string(.)`).\r\n-   `.text()`: Get a list of only the element's direct text nodes (`./text()`).\r\n-   `.parent()`: Get the parent element.\r\n-   `.next_sibling()` / `.prev_sibling()`: Get adjacent sibling elements.\r\n-   `.xpath(query)`: Execute a raw string or a constructed query within the context of this element.\r\n\r\n#### `XPathElementList` (Multiple Results)\r\n\r\n-   `.one()`: Ensures the list contains exactly one element and returns it; otherwise, raises an error.\r\n-   `.first()` / `.last()`: Get the first or last element; raises an error if the list is empty.\r\n-   `len(element_list)`: Get the number of elements.\r\n-   `.filter(func)`: Filter the list based on a function.\r\n-   `.map(func)`: Apply a function to each element and return a list of the results.\r\n-   Can be iterated over directly: `for e in my_list: ...`\r\n-   Supports slicing and indexing: `my_list[0]`, `my_list[-1]`\r\n\r\n### 6. DOM Manipulation\r\n\r\nModify the document tree with ease.\r\n\r\n```python\r\nfrom xpathkit import XPathElement, E, A\r\n\r\n# Assuming 'root' is a parsed XPathElement\r\n# Find the <ul> element\r\nul = root.descendant(E.ul)\r\n\r\n# Create and append a new <li>\r\nnew_li = XPathElement.create(\"li\", attr={\"class\": \"new-item\"}, text=\"Item 4\")\r\nul.append(new_li)\r\n\r\n# Remove an element\r\nitem_to_remove = ul.child(E.li[A.class_.contains(\"disabled\")])\r\nif item_to_remove:\r\n    ul.remove(item_to_remove)\r\n\r\n# Print the modified HTML\r\nprint(root.tostring())\r\n```\r\n\r\n---\r\n\r\n## \ud83d\udcc4 License\r\n\r\nThis project is licensed under the MIT License. See the `LICENSE` file for details.\r\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A toolkit for convenient and expressive XPath operations based on lxml.",
    "version": "0.3.5",
    "project_urls": null,
    "split_keywords": [
        "xpath",
        " lxml",
        " xml",
        " html",
        " parser"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "1257c35f15c39de812046c9dc84609446db3a7cb4885e2b8964b997b601fb2bc",
                "md5": "6a3b0829957a1bebb14ea678bb45f8d0",
                "sha256": "06b689b48b3a06ce5563afffe67aea219638f4629b5bd4468a59355c8e0dc86d"
            },
            "downloads": -1,
            "filename": "xpath_kit-0.3.5-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "6a3b0829957a1bebb14ea678bb45f8d0",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 14230,
            "upload_time": "2025-09-03T10:50:45",
            "upload_time_iso_8601": "2025-09-03T10:50:45.262267Z",
            "url": "https://files.pythonhosted.org/packages/12/57/c35f15c39de812046c9dc84609446db3a7cb4885e2b8964b997b601fb2bc/xpath_kit-0.3.5-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c2cadfca1edd91bec3d8fbcbd1289463f6f1d42cae32f6619ef97914d754c451",
                "md5": "3ac03eb98a7ff19b37725fe03fd611ec",
                "sha256": "f643bc4ed9b80de1a319fc12bd41a35783f6205d25c7fccf72c0356c73588f29"
            },
            "downloads": -1,
            "filename": "xpath_kit-0.3.5.tar.gz",
            "has_sig": false,
            "md5_digest": "3ac03eb98a7ff19b37725fe03fd611ec",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 19872,
            "upload_time": "2025-09-03T10:50:46",
            "upload_time_iso_8601": "2025-09-03T10:50:46.919785Z",
            "url": "https://files.pythonhosted.org/packages/c2/ca/dfca1edd91bec3d8fbcbd1289463f6f1d42cae32f6619ef97914d754c451/xpath_kit-0.3.5.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-09-03 10:50:46",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "xpath-kit"
}
        
Elapsed time: 0.87296s