grafana-foundation-sdk


Namegrafana-foundation-sdk JSON
Version 1733927908!10.1.0 PyPI version JSON
download
home_pageNone
SummaryA set of tools, types and libraries for building and manipulating Grafana objects.
upload_time2024-12-11 14:55:17
maintainerNone
docs_urlNone
authorGrafana Labs
requires_python>=3.11
licenseNone
keywords grafana logs metrics observability sdk traces
VCS
bugtrack_url
requirements Babel brotlicffi certifi cffi charset-normalizer click colorama ghp-import idna importlib_metadata Jinja2 Markdown MarkupSafe mergedeep mkdocs mkdocs-get-deps mkdocs-material mkdocs-material-extensions mkdocs-nav-weight mypy mypy-extensions packaging paginate pathspec platformdirs pycparser Pygments pymdown-extensions python-dateutil PyYAML pyyaml_env_tag regex requests six toml typing_extensions urllib3 watchdog zipp
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Grafana Foundation SDK – Python

A set of tools, types and *builder libraries* for building and manipulating Grafana objects in Python.

> [!NOTE]
> This branch contains **types and builders generated for Grafana v10.1.x.**
> Other supported versions of Grafana can be found at [this repository's root](https://github.com/grafana/grafana-foundation-sdk/).

## Installing

```shell
python3 -m pip install 'grafana_foundation_sdk==1733927908!10.1.0'
```

## Example usage

### Building a dashboard

```python
from grafana_foundation_sdk.builders.dashboard import Dashboard, Row
from grafana_foundation_sdk.builders.prometheus import Dataquery as PrometheusQuery
from grafana_foundation_sdk.builders.timeseries import Panel as Timeseries
from grafana_foundation_sdk.cog.encoder import JSONEncoder
from grafana_foundation_sdk.models.common import TimeZoneBrowser

def build_dashboard() -> Dashboard:
    builder = (
        Dashboard("[TEST] Node Exporter / Raspberry")
        .uid("test-dashboard-raspberry")
        .tags(["generated", "raspberrypi-node-integration"])

        .refresh("1m")
        .time("now-30m", "now")
        .timezone(TimeZoneBrowser)

        .with_row(Row("Overview"))
        .with_panel(
            Timeseries()
            .title("Network Received")
            .unit("bps")
            .min_val(0)
            .with_target(
                PrometheusQuery()
                .expr('rate(node_network_receive_bytes_total{job="integrations/raspberrypi-node", device!="lo"}[$__rate_interval]) * 8')
                .legend_format("{{ device }}")
            )
        )
    )

    return builder


if __name__ == '__main__':
    dashboard = build_dashboard().build()
    encoder = JSONEncoder(sort_keys=True, indent=2)

    print(encoder.encode(dashboard))
```

### Unmarshaling a dashboard

```python
import json

from grafana_foundation_sdk.cog.plugins import register_default_plugins
from grafana_foundation_sdk.models.dashboard import Dashboard as DashboardModel


if __name__ == '__main__':
    # Required to correctly unmarshal panels and dataqueries
    register_default_plugins()

    with open("dashboard.json", "r") as f:
        decoded_dashboard = DashboardModel.from_json(json.load(f))
        print(decoded_dashboard)
```

### Defining a custom query type

While the SDK ships with support for all core datasources and their query types,
it can be extended for private/third-party plugins.

To do so, define a type and a builder for the custom query:

```python
# src/customquery.py
from typing import Any, Optional, Self

from grafana_foundation_sdk.cog import variants as cogvariants
from grafana_foundation_sdk.cog import runtime as cogruntime
from grafana_foundation_sdk.cog import builder


class CustomQuery(cogvariants.Dataquery):
    # ref_id and hide are expected on all queries
    ref_id: Optional[str]
    hide: Optional[bool]

    # query is specific to the CustomQuery type
    query: str

    def __init__(self, query: str, ref_id: Optional[str] = None, hide: Optional[bool] = None):
        self.query = query
        self.ref_id = ref_id
        self.hide = hide

    def to_json(self) -> dict[str, object]:
        payload: dict[str, object] = {
            "query": self.query,
        }
        if self.ref_id is not None:
            payload["refId"] = self.ref_id
        if self.hide is not None:
            payload["hide"] = self.hide
        return payload

    @classmethod
    def from_json(cls, data: dict[str, Any]) -> Self:
        args: dict[str, Any] = {}

        if "query" in data:
            args["query"] = data["query"]
        if "refId" in data:
            args["ref_id"] = data["refId"]
        if "hide" in data:
            args["hide"] = data["hide"]

        return cls(**args)


def custom_query_variant_config() -> cogruntime.DataqueryConfig:
    return cogruntime.DataqueryConfig(
        # datasource plugin ID
        identifier="custom-query",
        from_json_hook=CustomQuery.from_json,
    )


class CustomQueryBuilder(builder.Builder[CustomQuery]):
    _internal: CustomQuery

    def __init__(self, query: str):
        self._internal = CustomQuery(query=query)

    def build(self) -> CustomQuery:
        return self._internal

    def ref_id(self, ref_id: str) -> Self:
        self._internal.ref_id = ref_id

        return self

    def hide(self, hide: bool) -> Self:
        self._internal.hide = hide

        return self
```

Register the type with cog, and use it as usual to build a dashboard:

```python
from grafana_foundation_sdk.builders.dashboard import Dashboard, Row
from grafana_foundation_sdk.builders.timeseries import Panel as Timeseries
from grafana_foundation_sdk.cog.encoder import JSONEncoder
from grafana_foundation_sdk.cog.plugins import register_default_plugins
from grafana_foundation_sdk.cog.runtime import register_dataquery_variant

from src.customquery import custom_query_variant_config, CustomQueryBuilder


if __name__ == '__main__':
    # Required to correctly unmarshal panels and dataqueries
    register_default_plugins()

    # This lets cog know about the newly created query type and how to unmarshal it.
    register_dataquery_variant(custom_query_variant_config())

    dashboard = (
        Dashboard("Custom query type")
        .uid("test-custom-query")
        .refresh("1m")
        .time("now-30m", "now")

        .with_row(Row("Overview"))
        .with_panel(
            Timeseries()
            .title("Sample panel")
            .with_target(
                CustomQueryBuilder("query here")
            )
        )
    ).build()

    print(JSONEncoder(sort_keys=True, indent=2).encode(dashboard))
```

### Defining a custom panel type

While the SDK ships with support for all core panels, it can be extended for
private/third-party plugins.

To do so, define a type and a builder for the custom panel's options:

```python
# src/custompanel.py
from typing import Any, Self

from grafana_foundation_sdk.cog import builder
from grafana_foundation_sdk.cog import runtime as cogruntime
from grafana_foundation_sdk.builders.dashboard import Panel as PanelBuilder
from grafana_foundation_sdk.models import dashboard


class CustomPanelOptions:
    make_beautiful: bool

    def __init__(self, make_beautiful: bool = False):
        self.make_beautiful = make_beautiful

    def to_json(self) -> dict[str, object]:
        return {
            "makeBeautiful": self.make_beautiful,
        }

    @classmethod
    def from_json(cls, data: dict[str, Any]) -> Self:
        args: dict[str, Any] = {}

        if "makeBeautiful" in data:
            args["make_beautiful"] = data["makeBeautiful"]

        return cls(**args)


def custom_panel_variant_config() -> cogruntime.PanelCfgConfig:
    return cogruntime.PanelCfgConfig(
        # plugin ID
        identifier="custom-panel",
        options_from_json_hook=CustomPanelOptions.from_json,
    )


class CustomPanelBuilder(PanelBuilder, builder.Builder[dashboard.Panel]):
    def __init__(self):
        super().__init__()
        # plugin ID
        self._internal.type_val = "custom-panel"

    def make_beautiful(self) -> Self:
        if self._internal.options is None:
            self._internal.options = CustomPanelOptions()

        assert isinstance(self._internal.options, CustomPanelOptions)

        self._internal.options.make_beautiful = True

        return self
```

Register the type with cog, and use it as usual to build a dashboard:

```python
from grafana_foundation_sdk.builders.dashboard import Dashboard, Row
from grafana_foundation_sdk.cog.encoder import JSONEncoder
from grafana_foundation_sdk.cog.plugins import register_default_plugins
from grafana_foundation_sdk.cog.runtime import register_panelcfg_variant

from src.custompanel import custom_panel_variant_config, CustomPanelBuilder


if __name__ == '__main__':
    # Required to correctly unmarshal panels and dataqueries
    register_default_plugins()

    # This lets cog know about the newly created panel type and how to unmarshal it.
    register_panelcfg_variant(custom_panel_variant_config())

    dashboard = (
        Dashboard("Custom panel type")
        .uid("test-custom-panel")
        .refresh("1m")
        .time("now-30m", "now")

        .with_row(Row("Overview"))
        .with_panel(
            CustomPanelBuilder()
            .title("Sample panel")
            .make_beautiful()
        )
    ).build()

    print(JSONEncoder(sort_keys=True, indent=2).encode(dashboard))
```

## Maturity

The code in this repository should be considered as "public preview". While it is used by Grafana Labs in production, it still is under active development.

> [!NOTE]
> Bugs and issues are handled solely by Engineering teams. On-call support or SLAs are not available.

## License

[Apache 2.0 License](./LICENSE)

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "grafana-foundation-sdk",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.11",
    "maintainer_email": null,
    "keywords": "grafana, logs, metrics, observability, sdk, traces",
    "author": "Grafana Labs",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/d8/f3/8b3ec4d0dd78e1d7c05c5467243bf2aeef2fec11143fb25869ae646fd9e7/grafana_foundation_sdk-1733927908!10.1.0.tar.gz",
    "platform": null,
    "description": "# Grafana Foundation SDK \u2013 Python\n\nA set of tools, types and *builder libraries* for building and manipulating Grafana objects in Python.\n\n> [!NOTE]\n> This branch contains **types and builders generated for Grafana v10.1.x.**\n> Other supported versions of Grafana can be found at [this repository's root](https://github.com/grafana/grafana-foundation-sdk/).\n\n## Installing\n\n```shell\npython3 -m pip install 'grafana_foundation_sdk==1733927908!10.1.0'\n```\n\n## Example usage\n\n### Building a dashboard\n\n```python\nfrom grafana_foundation_sdk.builders.dashboard import Dashboard, Row\nfrom grafana_foundation_sdk.builders.prometheus import Dataquery as PrometheusQuery\nfrom grafana_foundation_sdk.builders.timeseries import Panel as Timeseries\nfrom grafana_foundation_sdk.cog.encoder import JSONEncoder\nfrom grafana_foundation_sdk.models.common import TimeZoneBrowser\n\ndef build_dashboard() -> Dashboard:\n    builder = (\n        Dashboard(\"[TEST] Node Exporter / Raspberry\")\n        .uid(\"test-dashboard-raspberry\")\n        .tags([\"generated\", \"raspberrypi-node-integration\"])\n\n        .refresh(\"1m\")\n        .time(\"now-30m\", \"now\")\n        .timezone(TimeZoneBrowser)\n\n        .with_row(Row(\"Overview\"))\n        .with_panel(\n            Timeseries()\n            .title(\"Network Received\")\n            .unit(\"bps\")\n            .min_val(0)\n            .with_target(\n                PrometheusQuery()\n                .expr('rate(node_network_receive_bytes_total{job=\"integrations/raspberrypi-node\", device!=\"lo\"}[$__rate_interval]) * 8')\n                .legend_format(\"{{ device }}\")\n            )\n        )\n    )\n\n    return builder\n\n\nif __name__ == '__main__':\n    dashboard = build_dashboard().build()\n    encoder = JSONEncoder(sort_keys=True, indent=2)\n\n    print(encoder.encode(dashboard))\n```\n\n### Unmarshaling a dashboard\n\n```python\nimport json\n\nfrom grafana_foundation_sdk.cog.plugins import register_default_plugins\nfrom grafana_foundation_sdk.models.dashboard import Dashboard as DashboardModel\n\n\nif __name__ == '__main__':\n    # Required to correctly unmarshal panels and dataqueries\n    register_default_plugins()\n\n    with open(\"dashboard.json\", \"r\") as f:\n        decoded_dashboard = DashboardModel.from_json(json.load(f))\n        print(decoded_dashboard)\n```\n\n### Defining a custom query type\n\nWhile the SDK ships with support for all core datasources and their query types,\nit can be extended for private/third-party plugins.\n\nTo do so, define a type and a builder for the custom query:\n\n```python\n# src/customquery.py\nfrom typing import Any, Optional, Self\n\nfrom grafana_foundation_sdk.cog import variants as cogvariants\nfrom grafana_foundation_sdk.cog import runtime as cogruntime\nfrom grafana_foundation_sdk.cog import builder\n\n\nclass CustomQuery(cogvariants.Dataquery):\n    # ref_id and hide are expected on all queries\n    ref_id: Optional[str]\n    hide: Optional[bool]\n\n    # query is specific to the CustomQuery type\n    query: str\n\n    def __init__(self, query: str, ref_id: Optional[str] = None, hide: Optional[bool] = None):\n        self.query = query\n        self.ref_id = ref_id\n        self.hide = hide\n\n    def to_json(self) -> dict[str, object]:\n        payload: dict[str, object] = {\n            \"query\": self.query,\n        }\n        if self.ref_id is not None:\n            payload[\"refId\"] = self.ref_id\n        if self.hide is not None:\n            payload[\"hide\"] = self.hide\n        return payload\n\n    @classmethod\n    def from_json(cls, data: dict[str, Any]) -> Self:\n        args: dict[str, Any] = {}\n\n        if \"query\" in data:\n            args[\"query\"] = data[\"query\"]\n        if \"refId\" in data:\n            args[\"ref_id\"] = data[\"refId\"]\n        if \"hide\" in data:\n            args[\"hide\"] = data[\"hide\"]\n\n        return cls(**args)\n\n\ndef custom_query_variant_config() -> cogruntime.DataqueryConfig:\n    return cogruntime.DataqueryConfig(\n        # datasource plugin ID\n        identifier=\"custom-query\",\n        from_json_hook=CustomQuery.from_json,\n    )\n\n\nclass CustomQueryBuilder(builder.Builder[CustomQuery]):\n    _internal: CustomQuery\n\n    def __init__(self, query: str):\n        self._internal = CustomQuery(query=query)\n\n    def build(self) -> CustomQuery:\n        return self._internal\n\n    def ref_id(self, ref_id: str) -> Self:\n        self._internal.ref_id = ref_id\n\n        return self\n\n    def hide(self, hide: bool) -> Self:\n        self._internal.hide = hide\n\n        return self\n```\n\nRegister the type with cog, and use it as usual to build a dashboard:\n\n```python\nfrom grafana_foundation_sdk.builders.dashboard import Dashboard, Row\nfrom grafana_foundation_sdk.builders.timeseries import Panel as Timeseries\nfrom grafana_foundation_sdk.cog.encoder import JSONEncoder\nfrom grafana_foundation_sdk.cog.plugins import register_default_plugins\nfrom grafana_foundation_sdk.cog.runtime import register_dataquery_variant\n\nfrom src.customquery import custom_query_variant_config, CustomQueryBuilder\n\n\nif __name__ == '__main__':\n    # Required to correctly unmarshal panels and dataqueries\n    register_default_plugins()\n\n    # This lets cog know about the newly created query type and how to unmarshal it.\n    register_dataquery_variant(custom_query_variant_config())\n\n    dashboard = (\n        Dashboard(\"Custom query type\")\n        .uid(\"test-custom-query\")\n        .refresh(\"1m\")\n        .time(\"now-30m\", \"now\")\n\n        .with_row(Row(\"Overview\"))\n        .with_panel(\n            Timeseries()\n            .title(\"Sample panel\")\n            .with_target(\n                CustomQueryBuilder(\"query here\")\n            )\n        )\n    ).build()\n\n    print(JSONEncoder(sort_keys=True, indent=2).encode(dashboard))\n```\n\n### Defining a custom panel type\n\nWhile the SDK ships with support for all core panels, it can be extended for\nprivate/third-party plugins.\n\nTo do so, define a type and a builder for the custom panel's options:\n\n```python\n# src/custompanel.py\nfrom typing import Any, Self\n\nfrom grafana_foundation_sdk.cog import builder\nfrom grafana_foundation_sdk.cog import runtime as cogruntime\nfrom grafana_foundation_sdk.builders.dashboard import Panel as PanelBuilder\nfrom grafana_foundation_sdk.models import dashboard\n\n\nclass CustomPanelOptions:\n    make_beautiful: bool\n\n    def __init__(self, make_beautiful: bool = False):\n        self.make_beautiful = make_beautiful\n\n    def to_json(self) -> dict[str, object]:\n        return {\n            \"makeBeautiful\": self.make_beautiful,\n        }\n\n    @classmethod\n    def from_json(cls, data: dict[str, Any]) -> Self:\n        args: dict[str, Any] = {}\n\n        if \"makeBeautiful\" in data:\n            args[\"make_beautiful\"] = data[\"makeBeautiful\"]\n\n        return cls(**args)\n\n\ndef custom_panel_variant_config() -> cogruntime.PanelCfgConfig:\n    return cogruntime.PanelCfgConfig(\n        # plugin ID\n        identifier=\"custom-panel\",\n        options_from_json_hook=CustomPanelOptions.from_json,\n    )\n\n\nclass CustomPanelBuilder(PanelBuilder, builder.Builder[dashboard.Panel]):\n    def __init__(self):\n        super().__init__()\n        # plugin ID\n        self._internal.type_val = \"custom-panel\"\n\n    def make_beautiful(self) -> Self:\n        if self._internal.options is None:\n            self._internal.options = CustomPanelOptions()\n\n        assert isinstance(self._internal.options, CustomPanelOptions)\n\n        self._internal.options.make_beautiful = True\n\n        return self\n```\n\nRegister the type with cog, and use it as usual to build a dashboard:\n\n```python\nfrom grafana_foundation_sdk.builders.dashboard import Dashboard, Row\nfrom grafana_foundation_sdk.cog.encoder import JSONEncoder\nfrom grafana_foundation_sdk.cog.plugins import register_default_plugins\nfrom grafana_foundation_sdk.cog.runtime import register_panelcfg_variant\n\nfrom src.custompanel import custom_panel_variant_config, CustomPanelBuilder\n\n\nif __name__ == '__main__':\n    # Required to correctly unmarshal panels and dataqueries\n    register_default_plugins()\n\n    # This lets cog know about the newly created panel type and how to unmarshal it.\n    register_panelcfg_variant(custom_panel_variant_config())\n\n    dashboard = (\n        Dashboard(\"Custom panel type\")\n        .uid(\"test-custom-panel\")\n        .refresh(\"1m\")\n        .time(\"now-30m\", \"now\")\n\n        .with_row(Row(\"Overview\"))\n        .with_panel(\n            CustomPanelBuilder()\n            .title(\"Sample panel\")\n            .make_beautiful()\n        )\n    ).build()\n\n    print(JSONEncoder(sort_keys=True, indent=2).encode(dashboard))\n```\n\n## Maturity\n\nThe code in this repository should be considered as \"public preview\". While it is used by Grafana Labs in production, it still is under active development.\n\n> [!NOTE]\n> Bugs and issues are handled solely by Engineering teams. On-call support or SLAs are not available.\n\n## License\n\n[Apache 2.0 License](./LICENSE)\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "A set of tools, types and libraries for building and manipulating Grafana objects.",
    "version": "1733927908!10.1.0",
    "project_urls": {
        "Homepage": "https://github.com/grafana/grafana-foundation-sdk",
        "Issues": "https://github.com/grafana/grafana-foundation-sdk/issues",
        "Repository": "https://github.com/grafana/grafana-foundation-sdk.git"
    },
    "split_keywords": [
        "grafana",
        " logs",
        " metrics",
        " observability",
        " sdk",
        " traces"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "81790c210e324dcea0145adca32a840f1b98a8273f22e8e0a23a8c5814f607fb",
                "md5": "e1608c8e0733b5db117303cd196f9b37",
                "sha256": "b95798ad227aeaba0189a787bb257e52bce10a4bfa95b2f56d45bbcba82867c3"
            },
            "downloads": -1,
            "filename": "grafana_foundation_sdk-1733927908!10.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "e1608c8e0733b5db117303cd196f9b37",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.11",
            "size": 302773,
            "upload_time": "2024-12-11T14:53:53",
            "upload_time_iso_8601": "2024-12-11T14:53:53.775680Z",
            "url": "https://files.pythonhosted.org/packages/81/79/0c210e324dcea0145adca32a840f1b98a8273f22e8e0a23a8c5814f607fb/grafana_foundation_sdk-1733927908!10.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "d8f38b3ec4d0dd78e1d7c05c5467243bf2aeef2fec11143fb25869ae646fd9e7",
                "md5": "d9c1804e4175abb87cd1894cf9c3ad2b",
                "sha256": "0d59354be3783319b48e5499b8e5e540dafa6e7c8c85b85c9f62ce5f65e5f4ab"
            },
            "downloads": -1,
            "filename": "grafana_foundation_sdk-1733927908!10.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "d9c1804e4175abb87cd1894cf9c3ad2b",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.11",
            "size": 370461,
            "upload_time": "2024-12-11T14:55:17",
            "upload_time_iso_8601": "2024-12-11T14:55:17.976194Z",
            "url": "https://files.pythonhosted.org/packages/d8/f3/8b3ec4d0dd78e1d7c05c5467243bf2aeef2fec11143fb25869ae646fd9e7/grafana_foundation_sdk-1733927908!10.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-11 14:55:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "grafana",
    "github_project": "grafana-foundation-sdk",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [
        {
            "name": "Babel",
            "specs": [
                [
                    "==",
                    "2.15.0"
                ]
            ]
        },
        {
            "name": "brotlicffi",
            "specs": [
                [
                    "==",
                    "1.1.0.0"
                ]
            ]
        },
        {
            "name": "certifi",
            "specs": [
                [
                    "==",
                    "2024.7.4"
                ]
            ]
        },
        {
            "name": "cffi",
            "specs": [
                [
                    "==",
                    "1.17.1"
                ]
            ]
        },
        {
            "name": "charset-normalizer",
            "specs": [
                [
                    "==",
                    "3.3.2"
                ]
            ]
        },
        {
            "name": "click",
            "specs": [
                [
                    "==",
                    "8.1.7"
                ]
            ]
        },
        {
            "name": "colorama",
            "specs": [
                [
                    "==",
                    "0.4.6"
                ]
            ]
        },
        {
            "name": "ghp-import",
            "specs": [
                [
                    "==",
                    "2.1.0"
                ]
            ]
        },
        {
            "name": "idna",
            "specs": [
                [
                    "==",
                    "3.7"
                ]
            ]
        },
        {
            "name": "importlib_metadata",
            "specs": [
                [
                    "==",
                    "7.1.0"
                ]
            ]
        },
        {
            "name": "Jinja2",
            "specs": [
                [
                    "==",
                    "3.1.4"
                ]
            ]
        },
        {
            "name": "Markdown",
            "specs": [
                [
                    "==",
                    "3.7"
                ]
            ]
        },
        {
            "name": "MarkupSafe",
            "specs": [
                [
                    "==",
                    "2.1.5"
                ]
            ]
        },
        {
            "name": "mergedeep",
            "specs": [
                [
                    "==",
                    "1.3.4"
                ]
            ]
        },
        {
            "name": "mkdocs",
            "specs": [
                [
                    "==",
                    "1.6.1"
                ]
            ]
        },
        {
            "name": "mkdocs-get-deps",
            "specs": [
                [
                    "==",
                    "0.2.0"
                ]
            ]
        },
        {
            "name": "mkdocs-material",
            "specs": [
                [
                    "==",
                    "9.5.39"
                ]
            ]
        },
        {
            "name": "mkdocs-material-extensions",
            "specs": [
                [
                    "==",
                    "1.3.1"
                ]
            ]
        },
        {
            "name": "mkdocs-nav-weight",
            "specs": [
                [
                    "==",
                    "0.2.0"
                ]
            ]
        },
        {
            "name": "mypy",
            "specs": [
                [
                    "==",
                    "1.10.0"
                ]
            ]
        },
        {
            "name": "mypy-extensions",
            "specs": [
                [
                    "==",
                    "1.0.0"
                ]
            ]
        },
        {
            "name": "packaging",
            "specs": [
                [
                    "==",
                    "24.1"
                ]
            ]
        },
        {
            "name": "paginate",
            "specs": [
                [
                    "==",
                    "0.5.7"
                ]
            ]
        },
        {
            "name": "pathspec",
            "specs": [
                [
                    "==",
                    "0.12.1"
                ]
            ]
        },
        {
            "name": "platformdirs",
            "specs": [
                [
                    "==",
                    "4.2.2"
                ]
            ]
        },
        {
            "name": "pycparser",
            "specs": [
                [
                    "==",
                    "2.22"
                ]
            ]
        },
        {
            "name": "Pygments",
            "specs": [
                [
                    "==",
                    "2.18.0"
                ]
            ]
        },
        {
            "name": "pymdown-extensions",
            "specs": [
                [
                    "==",
                    "10.11.2"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    "==",
                    "2.9.0.post0"
                ]
            ]
        },
        {
            "name": "PyYAML",
            "specs": [
                [
                    "==",
                    "6.0.2"
                ]
            ]
        },
        {
            "name": "pyyaml_env_tag",
            "specs": [
                [
                    "==",
                    "0.1"
                ]
            ]
        },
        {
            "name": "regex",
            "specs": [
                [
                    "==",
                    "2024.5.15"
                ]
            ]
        },
        {
            "name": "requests",
            "specs": [
                [
                    "==",
                    "2.32.3"
                ]
            ]
        },
        {
            "name": "six",
            "specs": [
                [
                    "==",
                    "1.16.0"
                ]
            ]
        },
        {
            "name": "toml",
            "specs": [
                [
                    "==",
                    "0.10.2"
                ]
            ]
        },
        {
            "name": "typing_extensions",
            "specs": [
                [
                    "==",
                    "4.12.2"
                ]
            ]
        },
        {
            "name": "urllib3",
            "specs": [
                [
                    "==",
                    "2.2.2"
                ]
            ]
        },
        {
            "name": "watchdog",
            "specs": [
                [
                    "==",
                    "4.0.1"
                ]
            ]
        },
        {
            "name": "zipp",
            "specs": [
                [
                    "==",
                    "3.19.2"
                ]
            ]
        }
    ],
    "lcname": "grafana-foundation-sdk"
}
        
Elapsed time: 0.40806s