diff --git a/.github/actions/run-appinspect/action.yml b/.github/actions/run-appinspect/action.yml new file mode 100644 index 00000000..6fea491f --- /dev/null +++ b/.github/actions/run-appinspect/action.yml @@ -0,0 +1,28 @@ +name: Run Splunk AppInspect +description: Package a mock app containing the SDK and its dependencies, then validate it with AppInspect. + +inputs: + mock-app-path: + description: Path to app packaged for scanning with AppInspect + required: true + default: ./tests/system/test_apps/generating_app + +runs: + using: composite + steps: + - name: Install AppInspect dependencies + shell: bash + run: sudo apt-get install -y libmagic1 + - name: Install the SDK and its dependencies into the mock app + shell: bash + run: | + mkdir -p ${{ inputs.mock-app-path }}/bin/lib + uv pip install ".[openai, anthropic]" --target ${{ inputs.mock-app-path }}/bin/lib + - name: Package the mock app + shell: bash + run: | + cd ${{ inputs.mock-app-path }} + tar -czf mock_app.tgz --exclude="__pycache__" bin default metadata + - name: Validate the mock app with AppInspect + shell: bash + run: uvx splunk-appinspect inspect ${{ inputs.mock-app-path }}/mock_app.tgz --included-tags cloud diff --git a/.github/actions/setup-sdk-environment/action.yml b/.github/actions/setup-sdk-environment/action.yml index b66cdd9e..d62adbe1 100644 --- a/.github/actions/setup-sdk-environment/action.yml +++ b/.github/actions/setup-sdk-environment/action.yml @@ -5,9 +5,11 @@ inputs: python-version: description: Python version used for this run required: true + default: "3.13" deps-group: description: Dependency groups passed to `uv sync --group` required: true + default: lint runs: using: composite @@ -15,8 +17,12 @@ runs: - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 with: version: 0.11.6 - activate-environment: true python-version: ${{ inputs.python-version }} - - name: Install dependencies from the ${{ inputs.deps-group }} group(s) - run: SDK_DEPS_GROUP="${{ inputs.deps-group }}" make uv-sync-ci + activate-environment: true + enable-cache: true + cache-python: true + - name: Install dependencies from the ${{ inputs.deps-group }} group shell: bash + env: + SDK_DEPS_GROUP: ${{ inputs.deps-group }} + run: make uv-sync-ci diff --git a/.github/workflows/appinspect.yml b/.github/workflows/appinspect.yml index 17cb5007..f6088229 100644 --- a/.github/workflows/appinspect.yml +++ b/.github/workflows/appinspect.yml @@ -1,9 +1,8 @@ name: Validate SDK with Splunk AppInspect -on: [ push, workflow_dispatch ] +on: [push, workflow_dispatch] env: PYTHON_VERSION: 3.13 - MOCK_APP_PATH: ./tests/system/test_apps/generating_app jobs: appinspect: @@ -14,16 +13,5 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} deps-group: lint - - name: Install splunk-appinspect dependencies - run: sudo apt-get install -y libmagic1 - - name: Install packages for the mock app - run: | - mkdir -p ${{ env.MOCK_APP_PATH }}/bin/lib - uv pip install ".[openai, anthropic]" --target ${{ env.MOCK_APP_PATH }}/bin/lib - - name: Copy splunklib to a test app and package it as a mock app - run: | - cd ${{ env.MOCK_APP_PATH }} - tar -czf mock_app.tgz --exclude="__pycache__" bin default metadata - - name: Validate mock app with splunk-appinspect - run: uvx splunk-appinspect inspect ${{ env.MOCK_APP_PATH }}/mock_app.tgz - --included-tags cloud + - name: Run AppInspect + uses: ./.github/actions/run-appinspect diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 1a268ea3..fe932599 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -1,14 +1,19 @@ -name: Publish SDK to Test PyPI -on: [workflow_dispatch] +name: Publish pre-release to Test PyPI +on: [push] + # push: + # branches: + # - develop + # - release/2.x + # workflow_dispatch: env: PYTHON_VERSION: 3.13 jobs: - publish-to-test-pypi: + publish-pre-release: runs-on: ubuntu-latest permissions: - id-token: write + id-token: write # Required for publishing environment: name: splunk-test-pypi steps: @@ -17,9 +22,17 @@ jobs: with: python-version: ${{ env.PYTHON_VERSION }} deps-group: release + - name: Set temporary pre-release version + run: | + VERSION_BASE="$(uv version --short)" + RUN_NUMBER="${{ github.run_number }}" + COMMIT_SHA="$(git rev-parse --short HEAD)" + uv version --frozen "${VERSION_BASE}.dev${RUN_NUMBER}+g${COMMIT_SHA}" - name: Build packages for distribution run: uv build - - name: Publish packages to Test PyPI - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b - with: - repository-url: https://test.pypi.org/legacy/ + - name: Run AppInspect + uses: ./.github/actions/run-appinspect + # - name: Publish packages to Test PyPI + # uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b + # with: + # repository-url: https://test.pypi.org/legacy/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 988322ba..3c591615 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,7 +10,7 @@ jobs: publish-to-pypi: runs-on: ubuntu-latest permissions: - id-token: write + id-token: write # Required for publishing environment: name: splunk-pypi steps: @@ -21,6 +21,8 @@ jobs: deps-group: release - name: Build packages for distribution run: uv build + - name: Run AppInspect + uses: ./.github/actions/run-appinspect - name: Publish packages to PyPI uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 567c4954..56097e37 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,55 +1,55 @@ -name: Python SDK CI -on: [push, workflow_dispatch] +# name: Python SDK CI +# on: [push, workflow_dispatch] -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true +# concurrency: +# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} +# cancel-in-progress: true -jobs: - test-stage: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - python-version: [3.13] - splunk-version: [latest] - steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - uses: ./.github/actions/setup-sdk-environment - with: - python-version: ${{ matrix.python-version }} - deps-group: test - - name: Download Splunk MCP Server App - run: uv run ./scripts/download_splunk_mcp_server_app.py - env: - SPLUNKBASE_USERNAME: ${{ secrets.SPLUNKBASE_USERNAME }} - SPLUNKBASE_PASSWORD: ${{ secrets.SPLUNKBASE_PASSWORD }} - - name: Launch Splunk Docker instance - run: SPLUNK_VERSION=${{ matrix.splunk-version }} docker compose up -d - - name: Set up .env - run: cp .env.template .env - - name: Write internal AI secrets to .env - run: | - echo "internal_ai_app_key=$INTERNAL_AI_APP_KEY" >> .env - echo "internal_ai_client_id=$INTERNAL_AI_CLIENT_ID" >> .env - echo "internal_ai_client_secret=$INTERNAL_AI_CLIENT_SECRET" >> .env - echo "internal_ai_token_url=$INTERNAL_AI_TOKEN_URL" >> .env - echo "internal_ai_base_url=$INTERNAL_AI_BASE_URL" >> .env - env: - INTERNAL_AI_APP_KEY: ${{ secrets.INTERNAL_AI_APP_KEY }} - INTERNAL_AI_CLIENT_ID: ${{ secrets.INTERNAL_AI_CLIENT_ID }} - INTERNAL_AI_CLIENT_SECRET: ${{ secrets.INTERNAL_AI_CLIENT_SECRET }} - INTERNAL_AI_TOKEN_URL: ${{ secrets.INTERNAL_AI_TOKEN_URL }} - INTERNAL_AI_BASE_URL: ${{ secrets.INTERNAL_AI_BASE_URL }} - - name: Restore pytest cache - if: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' }} - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae - with: - path: .pytest_cache - key: pytest-cache-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.ref_name }}-${{ github.sha }} - restore-keys: | - pytest-cache-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.ref_name }}- - - name: Run unit tests - run: make test-unit - - name: Run entire test suite - run: make test-integration +# jobs: +# test-stage: +# strategy: +# matrix: +# os: [ubuntu-latest] +# python-version: [3.13] +# splunk-version: [latest] +# runs-on: ${{ matrix.os }} +# steps: +# - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd +# - uses: ./.github/actions/setup-sdk-environment +# with: +# python-version: ${{ matrix.python-version }} +# deps-group: test +# - name: Download Splunk MCP Server App +# env: +# SPLUNKBASE_USERNAME: ${{ secrets.SPLUNKBASE_USERNAME }} +# SPLUNKBASE_PASSWORD: ${{ secrets.SPLUNKBASE_PASSWORD }} +# run: uv run ./scripts/download_splunk_mcp_server_app.py +# - name: Launch Splunk Docker instance +# run: SPLUNK_VERSION=${{ matrix.splunk-version }} docker compose up -d +# - name: Set up .env +# run: cp .env.template .env +# - name: Write internal AI secrets to .env +# env: +# INTERNAL_AI_APP_KEY: ${{ secrets.INTERNAL_AI_APP_KEY }} +# INTERNAL_AI_CLIENT_ID: ${{ secrets.INTERNAL_AI_CLIENT_ID }} +# INTERNAL_AI_CLIENT_SECRET: ${{ secrets.INTERNAL_AI_CLIENT_SECRET }} +# INTERNAL_AI_TOKEN_URL: ${{ secrets.INTERNAL_AI_TOKEN_URL }} +# INTERNAL_AI_BASE_URL: ${{ secrets.INTERNAL_AI_BASE_URL }} +# run: | +# echo "internal_ai_app_key=$INTERNAL_AI_APP_KEY" >> .env +# echo "internal_ai_client_id=$INTERNAL_AI_CLIENT_ID" >> .env +# echo "internal_ai_client_secret=$INTERNAL_AI_CLIENT_SECRET" >> .env +# echo "internal_ai_token_url=$INTERNAL_AI_TOKEN_URL" >> .env +# echo "internal_ai_base_url=$INTERNAL_AI_BASE_URL" >> .env +# - name: Restore pytest cache +# if: ${{ github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' }} +# uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae +# with: +# path: .pytest_cache +# key: pytest-cache-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.ref_name }}-${{ github.sha }} +# restore-keys: | +# pytest-cache-${{ runner.os }}-py${{ matrix.python-version }}-${{ github.ref_name }}- +# - name: Run unit tests +# run: make test-unit +# - name: Run entire test suite +# run: make test-integration diff --git a/docs/conf.py b/docs/conf.py index 20d094dd..48e2cee4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,10 +9,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import importlib.metadata from datetime import datetime -import splunklib - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. @@ -40,17 +39,17 @@ master_doc = "index" # General information about the project. -project = "Splunk SDK for Python" -copyright = f"{datetime.now().year}, Splunk Inc." +package_name = "splunk-sdk" +project = f"Splunk SDK for Python (f{package_name}/splunklib)" +copyright = f"2011-{datetime.now().year} Splunk, Inc." # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = splunklib.__version__ -# The full version, including alpha/beta/rc tags. -release = splunklib.__version__ +release = importlib.metadata.version(package_name) +version = ".".join(release.split(".")[:2]) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pyproject.toml b/pyproject.toml index 708f0d14..aef9cdd7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ download = "https://github.com/splunk/splunk-sdk-python/releases/latest" [project] name = "splunk-sdk" -dynamic = ["version"] +version = "3.0.0" description = "Splunk Software Development Kit for Python" readme = "README.md" requires-python = ">=3.13" @@ -57,8 +57,7 @@ dev = [ ] [build-system] -# setuptools v61 introduces pyproject.toml support -requires = ["setuptools>=61.0.0"] +requires = ["setuptools>=82.0.1"] build-backend = "setuptools.build_meta" [tool.setuptools] @@ -71,9 +70,6 @@ packages = [ "splunklib.ai.engines", ] -[tool.setuptools.dynamic] -version = { attr = "splunklib.__version__" } - [tool.basedpyright] exclude = [".venv"] allowedUntypedLibraries = ["splunklib"] diff --git a/splunklib/__init__.py b/splunklib/__init__.py index a6639738..fc83e84a 100644 --- a/splunklib/__init__.py +++ b/splunklib/__init__.py @@ -30,7 +30,3 @@ def setup_logging( level, log_format=DEFAULT_LOG_FORMAT, date_format=DEFAULT_DATE_FORMAT ): logging.basicConfig(level=level, format=log_format, datefmt=date_format) - - -__version_info__ = (3, 0, 0) -__version__ = ".".join(map(str, __version_info__)) diff --git a/splunklib/binding.py b/splunklib/binding.py index 1684a50e..39ea2b2a 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -24,6 +24,7 @@ :mod:`splunklib.client` module. """ +import importlib.metadata import io import json import logging @@ -34,14 +35,13 @@ from contextlib import contextmanager from datetime import datetime from functools import wraps -from io import BytesIO -from urllib import parse from http import client from http.cookies import SimpleCookie +from io import BytesIO +from urllib import parse from xml.etree.ElementTree import XML, ParseError -from .data import record -from . import __version__ +from .data import record logger = logging.getLogger(__name__) @@ -1787,9 +1787,11 @@ def connect(scheme, host, port): def request(url, message, **kwargs): scheme, host, port, path = _spliturl(url) body = message.get("body", "") + + sdk_version = importlib.metadata.version("splunk-sdk") head = { "Content-Length": str(len(body)), - "User-Agent": "splunk-sdk-python/%s" % __version__, + "User-Agent": f"splunk-sdk-python/{sdk_version}", "Accept": "*/*", "Connection": "Close", } # defaults diff --git a/splunklib/client.py b/splunklib/client.py index 8e745442..9b7a07f4 100644 --- a/splunklib/client.py +++ b/splunklib/client.py @@ -32,10 +32,10 @@ jobs, saved searches, inputs, and indexes). All of these fields are :class:`Collection` objects:: - appcollection = service.apps - my_app = appcollection.create('my_app') - my_app = appcollection['my_app'] - appcollection.delete('my_app') + app_collection = service.apps + my_app = app_collection.create('my_app') + my_app = app_collection['my_app'] + app_collection.delete('my_app') The individual elements of the collection, in this case *applications*, are subclasses of :class:`Entity`. An ``Entity`` object has fields for its @@ -887,7 +887,7 @@ def get(self, path_segment="", owner=None, app=None, sharing=None, **query): ('content-type', 'text/xml; charset=utf-8')], 'reason': 'OK', 'status': 200} - apps.get('nonexistant/path') # raises HTTPError + apps.get('nonexistent/path') # raises HTTPError s.logout() apps.get() # raises AuthenticationError """ @@ -969,7 +969,7 @@ def post(self, path_segment="", owner=None, app=None, sharing=None, **query): ('content-type', 'text/xml; charset=utf-8')], 'reason': 'Created', 'status': 201} - apps.get('nonexistant/path') # raises HTTPError + apps.get('nonexistent/path') # raises HTTPError s.logout() apps.get() # raises AuthenticationError """ @@ -1468,7 +1468,7 @@ def __iter__(self, **kwargs): :type kwargs: ``dict`` :rtype: iterator over entities. - Implemented to give Collection a listish interface. This + Implemented to give Collection a list-ish interface. This function always makes a roundtrip to the server, plus at most two additional round trips if the ``autologin`` field of :func:`connect` is set to ``True``. @@ -1488,7 +1488,7 @@ def __iter__(self, **kwargs): def __len__(self): """Enable ``len(...)`` for ``Collection`` objects. - Implemented for consistency with a listish interface. No + Implemented for consistency with a list-ish interface. No further failure modes beyond those possible for any method on an Endpoint. @@ -1867,7 +1867,7 @@ def get(self, name="", owner=None, app=None, sharing=None, **query): ('content-type', 'text/xml; charset=utf-8')], 'reason': 'OK', 'status': 200} - saved_searches.get('nonexistant/search') # raises HTTPError + saved_searches.get('nonexistent/search') # raises HTTPError s.logout() saved_searches.get() # raises AuthenticationError @@ -1967,7 +1967,7 @@ def delete(self, key): def _entity_path(self, state): # Overridden to make all the ConfigurationFile objects # returned refer to the configs/ path instead of the - # properties/ path used by Configrations. + # properties/ path used by Configurations. return PATH_CONF % state["title"] @@ -3204,7 +3204,7 @@ def create(self, query, **kwargs): :param query: The search query. :type query: ``string`` - :param kwargs: Additiona parameters (optional). For a list of available + :param kwargs: Additional parameters (optional). For a list of available parameters, see `Search job parameters `_ on Splunk Developer Portal. diff --git a/uv.lock b/uv.lock index 348d058a..12b2b3cc 100644 --- a/uv.lock +++ b/uv.lock @@ -1646,6 +1646,7 @@ wheels = [ [[package]] name = "splunk-sdk" +version = "3.0.0" source = { editable = "." } [package.optional-dependencies]