[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6ACMAE92HSCR7VG9RXM4VTA":3,"skill-same:01M6ACMAE92HSCR7VG9RXM4VTA":75},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":31,"linkOnly":32,"markdownContent":33,"markdownBody":34,"frontMatter":35,"refName":37,"commitSha":38,"skillDirectory":39,"forksCount":40,"repositoryHtmlUrl":41,"repositoryHomepageUrl":42,"ownerHtmlUrl":43,"ownerBlogUrl":44,"ownerDescription":6,"updatedAt":45,"install":46,"resources":58},"01M6ACMAE92HSCR7VG9RXM4VTA","new-plugin",null,"Scaffold a new SkyWalking Python instrumentation plugin with all required files (plugin code, tests, docker-compose, expected data, services)","apache\u002Fskywalking-python","apache","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F47359?v=4","01M6AC83E6K4FDKK7HZ8WNMQKE","The Apache Software Foundation","01M6ACM8JFAMZWSSASQPBT71H6",220,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-python\u002Fblob\u002Fmaster\u002F.claude\u002Fskills\u002Fnew-plugin\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",[24,25,26,27,28,29,30],"plugin","new plugin","python","docker","scaffold","code","skywalking","2026-11-10 10:12:30",false,"---\nname: new-plugin\ndescription: Scaffold a new SkyWalking Python instrumentation plugin with all required files (plugin code, tests, docker-compose, expected data, services)\nuser-invocable: true\n---\n\n# New SkyWalking Python Plugin\n\nGenerate a complete instrumentation plugin for the SkyWalking Python agent. Ask the user for:\n\n1. **Library name** (e.g., `httpx`, `pymemcache`, `clickhouse-driver`)\n2. **Plugin type**: web framework (Entry spans), HTTP client (Exit spans), database (Exit spans), cache (Exit spans), message queue (Entry+Exit spans), RPC (Entry+Exit spans)\n3. **Library versions to test** (e.g., `['1.0', '2.0']`)\n4. **Minimum Python version** (default `>=3.7`)\n5. **External service needed for tests?** (e.g., Redis, MySQL, Kafka — or none for HTTP client plugins)\n\nIf the user provides a library name without details, research the library to determine the appropriate type and instrumentation points.\n\n## Files to Generate\n\n### 1. Plugin Module: `skywalking\u002Fplugins\u002Fsw_\u003Cname>.py`\n\nUse the Apache 2.0 license header (copy from any existing plugin file).\n\nFollow these patterns based on plugin type:\n\n**For HTTP Exit plugins** (like `sw_requests.py`):\n```python\nfrom skywalking import Layer, Component, config\nfrom skywalking.trace.context import get_context, NoopContext\nfrom skywalking.trace.span import NoopSpan\nfrom skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode\n\nlink_vector = ['\u003Cdocumentation URL>']\nsupport_matrix = {\n    '\u003Cpip-package-name>': {\n        '>=3.13': ['\u003Cmajor>.*'],  # use .* wildcard for latest patch (e.g., '4.*')\n        '>=3.10': ['\u003Colder_minor>.*', '\u003Cmajor>.*'],\n    }\n}\nnote = \"\"\"\"\"\"\n\ndef install():\n    from \u003Clibrary> import \u003CClass>\n    _original = \u003CClass>.\u003Cmethod>\n\n    def _sw_method(this, *args, **kwargs):\n        # Parse URL\u002Fpeer from args\n        span = NoopSpan(NoopContext()) if config.ignore_http_method_check(method) \\\n            else get_context().new_exit_span(op=path, peer=netloc, component=Component.\u003CName>)\n\n        with span:\n            carrier = span.inject()\n            span.layer = Layer.Http\n            # Inject carrier into outgoing headers\n            for item in carrier:\n                headers[item.key] = item.val\n            span.tag(TagHttpMethod(method))\n            span.tag(TagHttpURL(url))\n            res = _original(this, *args, **kwargs)\n            span.tag(TagHttpStatusCode(res.status_code))\n            if res.status_code >= 400:\n                span.error_occurred = True\n            return res\n\n    \u003CClass>.\u003Cmethod> = _sw_method\n```\n\n**For Web Entry plugins** (like `sw_flask.py`):\n```python\nfrom skywalking import Layer, Component, config\nfrom skywalking.trace.carrier import Carrier\nfrom skywalking.trace.context import get_context, NoopContext\nfrom skywalking.trace.span import NoopSpan\nfrom skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode\n\ndef install():\n    from \u003Cframework> import \u003CApp>\n    _original = \u003CApp>.\u003Chandler_method>\n\n    def _sw_handler(this, *args, **kwargs):\n        req = \u003Cget_request>\n        carrier = Carrier()\n        for item in carrier:\n            if item.key.capitalize() in req.headers:\n                item.val = req.headers[item.key.capitalize()]\n\n        span = NoopSpan(NoopContext()) if config.ignore_http_method_check(req.method) \\\n            else get_context().new_entry_span(op=req.path, carrier=carrier, inherit=Component.General)\n\n        with span:\n            span.layer = Layer.Http\n            span.component = Component.\u003CName>\n            span.peer = f\"{remote_addr}:{remote_port}\"\n            span.tag(TagHttpMethod(req.method))\n            span.tag(TagHttpURL(req.url))\n            resp = _original(this, *args, **kwargs)\n            span.tag(TagHttpStatusCode(resp.status_code))\n            if resp.status_code >= 400:\n                span.error_occurred = True\n            return resp\n\n    \u003CApp>.\u003Chandler_method> = _sw_handler\n```\n\n**For Database\u002FCache Exit plugins** (like `sw_redis.py`):\n```python\nfrom skywalking import Layer, Component\nfrom skywalking.trace.context import get_context\nfrom skywalking.trace.tags import TagDbType, TagDbInstance, TagDbStatement  # or TagCacheType, TagCacheOp, etc.\n\ndef install():\n    from \u003Clibrary> import \u003CConnection>\n    _original = \u003CConnection>.\u003Cmethod>\n\n    def _sw_method(this, *args, **kwargs):\n        peer = f'{this.host}:{this.port}'\n        with get_context().new_exit_span(op='\u003CDB>\u002F\u003Coperation>', peer=peer, component=Component.\u003CName>) as span:\n            span.layer = Layer.Database  # or Layer.Cache\n            span.tag(TagDbType('\u003CDB type>'))\n            span.tag(TagDbStatement(query))\n            res = _original(this, *args, **kwargs)\n            return res\n\n    \u003CConnection>.\u003Cmethod> = _sw_method\n```\n\n**For MQ plugins** (producer Exit + consumer Entry):\n- Producer: new_exit_span, inject carrier into message headers, Layer.MQ\n- Consumer: new_entry_span, extract carrier from message headers, Layer.MQ\n\n**For async plugins**: Use `async def` wrappers, `await` the original call.\n\n**For C extension libraries**: Use `wrapt.ObjectProxy` pattern (see `sw_psycopg2.py`).\n\n### 2. Component Enum: `skywalking\u002F__init__.py`\n\nAdd a new entry to the `Component` enum. Check the last Python-specific ID (7000+) and increment. Example:\n```python\nNewLib = 7020  # Next available ID\n```\n\nIf the component already exists in the SkyWalking ecosystem (check existing enum), reuse that ID.\n\n### 3. Test Directory: `tests\u002Fplugin\u002F{data|http|web}\u002Fsw_\u003Cname>\u002F`\n\nChoose the subdirectory:\n- `web\u002F` for web frameworks\n- `http\u002F` for HTTP clients\n- `data\u002F` for databases, caches, message queues\n\nCreate these files:\n\n#### `__init__.py`\nEmpty file with license header.\n\n#### `test_\u003Cname>.py`\n```python\nfrom typing import Callable\nimport pytest\nimport requests\nfrom skywalking.plugins.sw_\u003Cname> import support_matrix\nfrom tests.orchestrator import get_test_vector\nfrom tests.plugin.base import TestPluginBase\n\n@pytest.fixture\ndef prepare():\n    return lambda *_: requests.get('http:\u002F\u002F0.0.0.0:9090\u002F\u003Cendpoint>', timeout=5)\n\nclass TestPlugin(TestPluginBase):\n    @pytest.mark.parametrize('version', get_test_vector(lib_name='\u003Cname>', support_matrix=support_matrix))\n    def test_plugin(self, docker_compose, version):\n        self.validate()\n```\n\n#### `docker-compose.yml`\n```yaml\nversion: '2.1'\n\nservices:\n  collector:\n    extends:\n      service: collector\n      file: ..\u002F..\u002Fdocker-compose.base.yml\n\n  # Add external service if needed (database, cache, MQ broker)\n  # \u003Cservice_name>:\n  #   image: \u003Cimage>\n  #   ports: [\u003Cport>:\u003Cport>]\n  #   healthcheck: ...\n  #   networks: [beyond]\n\n  provider:\n    extends:\n      service: agent\n      file: ..\u002F..\u002Fdocker-compose.base.yml\n    ports:\n      - 9091:9091\n    volumes:\n      - .:\u002Fapp\n    command: ['bash', '-c', 'pip install flask && pip install -r \u002Fapp\u002Frequirements.txt && sw-python run python3 \u002Fapp\u002Fservices\u002Fprovider.py']\n    depends_on:\n      collector:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD\", \"bash\", \"-c\", \"cat \u003C \u002Fdev\u002Fnull > \u002Fdev\u002Ftcp\u002F127.0.0.1\u002F9091\"]\n      interval: 5s\n      timeout: 60s\n      retries: 120\n    environment:\n      SW_AGENT_NAME: provider\n      SW_AGENT_LOGGING_LEVEL: DEBUG\n\n  consumer:\n    extends:\n      service: agent\n      file: ..\u002F..\u002Fdocker-compose.base.yml\n    ports:\n      - 9090:9090\n    volumes:\n      - .:\u002Fapp\n    command: ['bash', '-c', 'pip install flask && pip install -r \u002Fapp\u002Frequirements.txt && sw-python run python3 \u002Fapp\u002Fservices\u002Fconsumer.py']\n    depends_on:\n      collector:\n        condition: service_healthy\n      provider:\n        condition: service_healthy\n    environment:\n      SW_AGENT_NAME: consumer\n      SW_AGENT_LOGGING_LEVEL: DEBUG\n\nnetworks:\n  beyond:\n```\n\nKey points:\n- Consumer listens on port 9090, provider on 9091\n- Consumer calls provider to create cross-process trace\n- Use `sw-python run python3` to start with agent instrumentation\n- The consumer uses Flask to expose an HTTP endpoint for the test's `prepare()` fixture to hit\n- The provider uses the target library (e.g., makes a Redis call, DB query)\n- Both install the target library via `pip install -r \u002Fapp\u002Frequirements.txt`\n\n#### `services\u002Fprovider.py`\nFlask app on port 9091 that uses the target library. Example for a data plugin:\n```python\nif __name__ == '__main__':\n    from flask import Flask, jsonify\n    app = Flask(__name__)\n\n    @app.route('\u002Fendpoint', methods=['POST', 'GET'])\n    def handler():\n        import \u003Ctarget_library>\n        # Make a call using the target library\n        # e.g., client = redis.StrictRedis(host='redis'); client.get('key')\n        return jsonify({'status': 'ok'})\n\n    app.run(host='0.0.0.0', port=9091)\n```\n\n#### `services\u002Fconsumer.py`\nFlask app on port 9090 that calls the provider:\n```python\nimport requests\n\nif __name__ == '__main__':\n    from flask import Flask, jsonify\n    app = Flask(__name__)\n\n    @app.route('\u002Fendpoint', methods=['POST', 'GET'])\n    def handler():\n        res = requests.post('http:\u002F\u002Fprovider:9091\u002Fendpoint', timeout=5)\n        return jsonify(res.json())\n\n    app.run(host='0.0.0.0', port=9090)\n```\n\n#### `services\u002F__init__.py`\nEmpty file with license header.\n\n#### `expected.data.yml`\nDefine expected spans. Structure depends on plugin type:\n\nFor a **data\u002Fcache Exit** plugin (consumer -> provider -> external service):\n```yaml\nsegmentItems:\n  - serviceName: provider\n    segmentSize: 1\n    segments:\n      - segmentId: not null\n        spans:\n          - operationName: \u003CDB>\u002F\u003Coperation>\n            parentSpanId: 0\n            spanId: 1\n            spanLayer: Database  # or Cache\n            startTime: gt 0\n            endTime: gt 0\n            componentId: \u003CID>\n            spanType: Exit\n            peer: \u003Cservice>:\u003Cport>\n            skipAnalysis: false\n            tags:\n              - key: db.type\n                value: \u003Ctype>\n              - key: db.statement\n                value: \u003Cquery>\n          - operationName: \u002Fendpoint\n            parentSpanId: -1\n            spanId: 0\n            spanLayer: Http\n            startTime: gt 0\n            endTime: gt 0\n            componentId: 7001\n            spanType: Entry\n            peer: not null\n            skipAnalysis: false\n            tags:\n              - key: http.method\n                value: POST\n              - key: http.url\n                value: http:\u002F\u002Fprovider:9091\u002Fendpoint\n              - key: http.status_code\n                value: '200'\n            refs:\n              - parentEndpoint: \u002Fendpoint\n                networkAddress: 'provider:9091'\n                refType: CrossProcess\n                parentSpanId: 1\n                parentTraceSegmentId: not null\n                parentServiceInstance: not null\n                parentService: consumer\n                traceId: not null\n  - serviceName: consumer\n    segmentSize: 1\n    segments:\n      - segmentId: not null\n        spans:\n          - operationName: \u002Fendpoint\n            parentSpanId: 0\n            spanId: 1\n            spanLayer: Http\n            startTime: gt 0\n            endTime: gt 0\n            componentId: 7002\n            spanType: Exit\n            peer: provider:9091\n            skipAnalysis: false\n            tags:\n              - key: http.method\n                value: POST\n              - key: http.url\n                value: http:\u002F\u002Fprovider:9091\u002Fendpoint\n              - key: http.status_code\n                value: '200'\n          - operationName: \u002Fendpoint\n            parentSpanId: -1\n            spanId: 0\n            spanLayer: Http\n            startTime: gt 0\n            endTime: gt 0\n            componentId: 7001\n            spanType: Entry\n            peer: not null\n            skipAnalysis: false\n            tags:\n              - key: http.method\n                value: GET\n              - key: http.url\n                value: http:\u002F\u002F0.0.0.0:9090\u002Fendpoint\n              - key: http.status_code\n                value: '200'\n```\n\nImportant notes for expected data:\n- Spans within a segment are ordered **child-first** (highest spanId first)\n- Entry spans have `parentSpanId: -1`\n- Exit spans reference the entry span as parent\n- Cross-process refs link consumer exit span to provider entry span\n- `componentId` must match the Component enum value exactly\n- Consumer's exit span uses Requests component (7002) since it calls via `requests.post()`\n- Consumer's entry span uses Flask component (7001) since it receives via Flask\n\n### 4. Update `pyproject.toml`\n\nAdd the library to `[tool.poetry.group.plugins.dependencies]`:\n```bash\npoetry add \u003Clibrary> --group plugins\n```\n\n### 5. Regenerate Docs\n\n```bash\nmake doc-gen\n```\n\n### 6. Lint Check\n\n```bash\nmake lint\n```\n\n## Post-Generation Reminders\n\nAfter generating all files, remind the user:\n1. A new component ID may need to be registered in the [main SkyWalking repo's component-libraries.yml](https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking\u002Fblob\u002Fmaster\u002Foap-server\u002Fserver-starter\u002Fsrc\u002Fmain\u002Fresources\u002Fcomponent-libraries.yml) and a logo added to the [UI repo](https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-booster-ui\u002Ftree\u002Fmain\u002Fsrc\u002Fassets\u002Fimg\u002Ftechnologies)\n2. Run `make doc-gen` to regenerate Plugins.md\n3. Run `make lint` to verify code style\n4. Test locally: build the Docker image and run `poetry run pytest -v tests\u002Fplugin\u002F\u003Ccategory>\u002Fsw_\u003Cname>\u002F`\n5. All files need the Apache 2.0 license header\n","# New SkyWalking Python Plugin\n\nGenerate a complete instrumentation plugin for the SkyWalking Python agent. Ask the user for:\n\n1. **Library name** (e.g., `httpx`, `pymemcache`, `clickhouse-driver`)\n2. **Plugin type**: web framework (Entry spans), HTTP client (Exit spans), database (Exit spans), cache (Exit spans), message queue (Entry+Exit spans), RPC (Entry+Exit spans)\n3. **Library versions to test** (e.g., `['1.0', '2.0']`)\n4. **Minimum Python version** (default `>=3.7`)\n5. **External service needed for tests?** (e.g., Redis, MySQL, Kafka — or none for HTTP client plugins)\n\nIf the user provides a library name without details, research the library to determine the appropriate type and instrumentation points.\n\n## Files to Generate\n\n### 1. Plugin Module: `skywalking\u002Fplugins\u002Fsw_\u003Cname>.py`\n\nUse the Apache 2.0 license header (copy from any existing plugin file).\n\nFollow these patterns based on plugin type:\n\n**For HTTP Exit plugins** (like `sw_requests.py`):\n```python\nfrom skywalking import Layer, Component, config\nfrom skywalking.trace.context import get_context, NoopContext\nfrom skywalking.trace.span import NoopSpan\nfrom skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode\n\nlink_vector = ['\u003Cdocumentation URL>']\nsupport_matrix = {\n    '\u003Cpip-package-name>': {\n        '>=3.13': ['\u003Cmajor>.*'],  # use .* wildcard for latest patch (e.g., '4.*')\n        '>=3.10': ['\u003Colder_minor>.*', '\u003Cmajor>.*'],\n    }\n}\nnote = \"\"\"\"\"\"\n\ndef install():\n    from \u003Clibrary> import \u003CClass>\n    _original = \u003CClass>.\u003Cmethod>\n\n    def _sw_method(this, *args, **kwargs):\n        # Parse URL\u002Fpeer from args\n        span = NoopSpan(NoopContext()) if config.ignore_http_method_check(method) \\\n            else get_context().new_exit_span(op=path, peer=netloc, component=Component.\u003CName>)\n\n        with span:\n            carrier = span.inject()\n            span.layer = Layer.Http\n            # Inject carrier into outgoing headers\n            for item in carrier:\n                headers[item.key] = item.val\n            span.tag(TagHttpMethod(method))\n            span.tag(TagHttpURL(url))\n            res = _original(this, *args, **kwargs)\n            span.tag(TagHttpStatusCode(res.status_code))\n            if res.status_code >= 400:\n                span.error_occurred = True\n            return res\n\n    \u003CClass>.\u003Cmethod> = _sw_method\n```\n\n**For Web Entry plugins** (like `sw_flask.py`):\n```python\nfrom skywalking import Layer, Component, config\nfrom skywalking.trace.carrier import Carrier\nfrom skywalking.trace.context import get_context, NoopContext\nfrom skywalking.trace.span import NoopSpan\nfrom skywalking.trace.tags import TagHttpMethod, TagHttpURL, TagHttpStatusCode\n\ndef install():\n    from \u003Cframework> import \u003CApp>\n    _original = \u003CApp>.\u003Chandler_method>\n\n    def _sw_handler(this, *args, **kwargs):\n        req = \u003Cget_request>\n        carrier = Carrier()\n        for item in carrier:\n            if item.key.capitalize() in req.headers:\n                item.val = req.headers[item.key.capitalize()]\n\n        span = NoopSpan(NoopContext()) if config.ignore_http_method_check(req.method) \\\n            else get_context().new_entry_span(op=req.path, carrier=carrier, inherit=Component.General)\n\n        with span:\n            span.layer = Layer.Http\n            span.component = Component.\u003CName>\n            span.peer = f\"{remote_addr}:{remote_port}\"\n            span.tag(TagHttpMethod(req.method))\n            span.tag(TagHttpURL(req.url))\n            resp = _original(this, *args, **kwargs)\n            span.tag(TagHttpStatusCode(resp.status_code))\n            if resp.status_code >= 400:\n                span.error_occurred = True\n            return resp\n\n    \u003CApp>.\u003Chandler_method> = _sw_handler\n```\n\n**For Database\u002FCache Exit plugins** (like `sw_redis.py`):\n```python\nfrom skywalking import Layer, Component\nfrom skywalking.trace.context import get_context\nfrom skywalking.trace.tags import TagDbType, TagDbInstance, TagDbStatement  # or TagCacheType, TagCacheOp, etc.\n\ndef install():\n    from \u003Clibrary> import \u003CConnection>\n    _original = \u003CConnection>.\u003Cmethod>\n\n    def _sw_method(this, *args, **kwargs):\n        peer = f'{this.host}:{this.port}'\n        with get_context().new_exit_span(op='\u003CDB>\u002F\u003Coperation>', peer=peer, component=Component.\u003CName>) as span:\n            span.layer = Layer.Database  # or Layer.Cache\n            span.tag(TagDbType('\u003CDB type>'))\n            span.tag(TagDbStatement(query))\n            res = _original(this, *args, **kwargs)\n            return res\n\n    \u003CConnection>.\u003Cmethod> = _sw_method\n```\n\n**For MQ plugins** (producer Exit + consumer Entry):\n- Producer: new_exit_span, inject carrier into message headers, Layer.MQ\n- Consumer: new_entry_span, extract carrier from message headers, Layer.MQ\n\n**For async plugins**: Use `async def` wrappers, `await` the original call.\n\n**For C extension libraries**: Use `wrapt.ObjectProxy` pattern (see `sw_psycopg2.py`).\n\n### 2. Component Enum: `skywalking\u002F__init__.py`\n\nAdd a new entry to the `Component` enum. Check the last Python-specific ID (7000+) and increment. Example:\n```python\nNewLib = 7020  # Next available ID\n```\n\nIf the component already exists in the SkyWalking ecosystem (check existing enum), reuse that ID.\n\n### 3. Test Directory: `tests\u002Fplugin\u002F{data|http|web}\u002Fsw_\u003Cname>\u002F`\n\nChoose the subdirectory:\n- `web\u002F` for web frameworks\n- `http\u002F` for HTTP clients\n- `data\u002F` for databases, caches, message queues\n\nCreate these files:\n\n#### `__init__.py`\nEmpty file with license header.\n\n#### `test_\u003Cname>.py`\n```python\nfrom typing import Callable\nimport pytest\nimport requests\nfrom skywalking.plugins.sw_\u003Cname> import support_matrix\nfrom tests.orchestrator import get_test_vector\nfrom tests.plugin.base import TestPluginBase\n\n@pytest.fixture\ndef prepare():\n    return lambda *_: requests.get('http:\u002F\u002F0.0.0.0:9090\u002F\u003Cendpoint>', timeout=5)\n\nclass TestPlugin(TestPluginBase):\n    @pytest.mark.parametrize('version', get_test_vector(lib_name='\u003Cname>', support_matrix=support_matrix))\n    def test_plugin(self, docker_compose, version):\n        self.validate()\n```\n\n#### `docker-compose.yml`\n```yaml\nversion: '2.1'\n\nservices:\n  collector:\n    extends:\n      service: collector\n      file: ..\u002F..\u002Fdocker-compose.base.yml\n\n  # Add external service if needed (database, cache, MQ broker)\n  # \u003Cservice_name>:\n  #   image: \u003Cimage>\n  #   ports: [\u003Cport>:\u003Cport>]\n  #   healthcheck: ...\n  #   networks: [beyond]\n\n  provider:\n    extends:\n      service: agent\n      file: ..\u002F..\u002Fdocker-compose.base.yml\n    ports:\n      - 9091:9091\n    volumes:\n      - .:\u002Fapp\n    command: ['bash', '-c', 'pip install flask && pip install -r \u002Fapp\u002Frequirements.txt && sw-python run python3 \u002Fapp\u002Fservices\u002Fprovider.py']\n    depends_on:\n      collector:\n        condition: service_healthy\n    healthcheck:\n      test: [\"CMD\", \"bash\", \"-c\", \"cat \u003C \u002Fdev\u002Fnull > \u002Fdev\u002Ftcp\u002F127.0.0.1\u002F9091\"]\n      interval: 5s\n      timeout: 60s\n      retries: 120\n    environment:\n      SW_AGENT_NAME: provider\n      SW_AGENT_LOGGING_LEVEL: DEBUG\n\n  consumer:\n    extends:\n      service: agent\n      file: ..\u002F..\u002Fdocker-compose.base.yml\n    ports:\n      - 9090:9090\n    volumes:\n      - .:\u002Fapp\n    command: ['bash', '-c', 'pip install flask && pip install -r \u002Fapp\u002Frequirements.txt && sw-python run python3 \u002Fapp\u002Fservices\u002Fconsumer.py']\n    depends_on:\n      collector:\n        condition: service_healthy\n      provider:\n        condition: service_healthy\n    environment:\n      SW_AGENT_NAME: consumer\n      SW_AGENT_LOGGING_LEVEL: DEBUG\n\nnetworks:\n  beyond:\n```\n\nKey points:\n- Consumer listens on port 9090, provider on 9091\n- Consumer calls provider to create cross-process trace\n- Use `sw-python run python3` to start with agent instrumentation\n- The consumer uses Flask to expose an HTTP endpoint for the test's `prepare()` fixture to hit\n- The provider uses the target library (e.g., makes a Redis call, DB query)\n- Both install the target library via `pip install -r \u002Fapp\u002Frequirements.txt`\n\n#### `services\u002Fprovider.py`\nFlask app on port 9091 that uses the target library. Example for a data plugin:\n```python\nif __name__ == '__main__':\n    from flask import Flask, jsonify\n    app = Flask(__name__)\n\n    @app.route('\u002Fendpoint', methods=['POST', 'GET'])\n    def handler():\n        import \u003Ctarget_library>\n        # Make a call using the target library\n        # e.g., client = redis.StrictRedis(host='redis'); client.get('key')\n        return jsonify({'status': 'ok'})\n\n    app.run(host='0.0.0.0', port=9091)\n```\n\n#### `services\u002Fconsumer.py`\nFlask app on port 9090 that calls the provider:\n```python\nimport requests\n\nif __name__ == '__main__':\n    from flask import Flask, jsonify\n    app = Flask(__name__)\n\n    @app.route('\u002Fendpoint', methods=['POST', 'GET'])\n    def handler():\n        res = requests.post('http:\u002F\u002Fprovider:9091\u002Fendpoint', timeout=5)\n        return jsonify(res.json())\n\n    app.run(host='0.0.0.0', port=9090)\n```\n\n#### `services\u002F__init__.py`\nEmpty file with license header.\n\n#### `expected.data.yml`\nDefine expected spans. Structure depends on plugin type:\n\nFor a **data\u002Fcache Exit** plugin (consumer -> provider -> external service):\n```yaml\nsegmentItems:\n  - serviceName: provider\n    segmentSize: 1\n    segments:\n      - segmentId: not null\n        spans:\n          - operationName: \u003CDB>\u002F\u003Coperation>\n            parentSpanId: 0\n            spanId: 1\n            spanLayer: Database  # or Cache\n            startTime: gt 0\n            endTime: gt 0\n            componentId: \u003CID>\n            spanType: Exit\n            peer: \u003Cservice>:\u003Cport>\n            skipAnalysis: false\n            tags:\n              - key: db.type\n                value: \u003Ctype>\n              - key: db.statement\n                value: \u003Cquery>\n          - operationName: \u002Fendpoint\n            parentSpanId: -1\n            spanId: 0\n            spanLayer: Http\n            startTime: gt 0\n            endTime: gt 0\n            componentId: 7001\n            spanType: Entry\n            peer: not null\n            skipAnalysis: false\n            tags:\n              - key: http.method\n                value: POST\n              - key: http.url\n                value: http:\u002F\u002Fprovider:9091\u002Fendpoint\n              - key: http.status_code\n                value: '200'\n            refs:\n              - parentEndpoint: \u002Fendpoint\n                networkAddress: 'provider:9091'\n                refType: CrossProcess\n                parentSpanId: 1\n                parentTraceSegmentId: not null\n                parentServiceInstance: not null\n                parentService: consumer\n                traceId: not null\n  - serviceName: consumer\n    segmentSize: 1\n    segments:\n      - segmentId: not null\n        spans:\n          - operationName: \u002Fendpoint\n            parentSpanId: 0\n            spanId: 1\n            spanLayer: Http\n            startTime: gt 0\n            endTime: gt 0\n            componentId: 7002\n            spanType: Exit\n            peer: provider:9091\n            skipAnalysis: false\n            tags:\n              - key: http.method\n                value: POST\n              - key: http.url\n                value: http:\u002F\u002Fprovider:9091\u002Fendpoint\n              - key: http.status_code\n                value: '200'\n          - operationName: \u002Fendpoint\n            parentSpanId: -1\n            spanId: 0\n            spanLayer: Http\n            startTime: gt 0\n            endTime: gt 0\n            componentId: 7001\n            spanType: Entry\n            peer: not null\n            skipAnalysis: false\n            tags:\n              - key: http.method\n                value: GET\n              - key: http.url\n                value: http:\u002F\u002F0.0.0.0:9090\u002Fendpoint\n              - key: http.status_code\n                value: '200'\n```\n\nImportant notes for expected data:\n- Spans within a segment are ordered **child-first** (highest spanId first)\n- Entry spans have `parentSpanId: -1`\n- Exit spans reference the entry span as parent\n- Cross-process refs link consumer exit span to provider entry span\n- `componentId` must match the Component enum value exactly\n- Consumer's exit span uses Requests component (7002) since it calls via `requests.post()`\n- Consumer's entry span uses Flask component (7001) since it receives via Flask\n\n### 4. Update `pyproject.toml`\n\nAdd the library to `[tool.poetry.group.plugins.dependencies]`:\n```bash\npoetry add \u003Clibrary> --group plugins\n```\n\n### 5. Regenerate Docs\n\n```bash\nmake doc-gen\n```\n\n### 6. Lint Check\n\n```bash\nmake lint\n```\n\n## Post-Generation Reminders\n\nAfter generating all files, remind the user:\n1. A new component ID may need to be registered in the [main SkyWalking repo's component-libraries.yml](https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking\u002Fblob\u002Fmaster\u002Foap-server\u002Fserver-starter\u002Fsrc\u002Fmain\u002Fresources\u002Fcomponent-libraries.yml) and a logo added to the [UI repo](https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-booster-ui\u002Ftree\u002Fmain\u002Fsrc\u002Fassets\u002Fimg\u002Ftechnologies)\n2. Run `make doc-gen` to regenerate Plugins.md\n3. Run `make lint` to verify code style\n4. Test locally: build the Docker image and run `poetry run pytest -v tests\u002Fplugin\u002F\u003Ccategory>\u002Fsw_\u003Cname>\u002F`\n5. All files need the Apache 2.0 license header\n",{"name":5,"description":7,"user-invocable":36},true,"master","822db0379fa23bb5a1b2ac1077c68ba7bdbdc406",".claude\u002Fskills\u002Fnew-plugin",114,"https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-python","https:\u002F\u002Fskywalking.apache.org\u002F","https:\u002F\u002Fgithub.com\u002Fapache","https:\u002F\u002Fwww.apache.org\u002F","2026-11-03 14:54:11",{"source":8,"skillName":5,"commands":47,"prompt":57,"zipAvailable":36},[48,51,54],{"tool":49,"command":50},"npx","npx skills add apache\u002Fskywalking-python --skill new-plugin",{"tool":52,"command":53},"bunx","bunx skills add apache\u002Fskywalking-python --skill new-plugin",{"tool":55,"command":56},"pnpm","pnpm dlx skills add apache\u002Fskywalking-python --skill new-plugin","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-python\u002Fblob\u002Fmaster\u002F.claude\u002Fskills\u002Fnew-plugin\u002FSKILL.md",[59,65,71],{"path":60,"resourceType":61,"resourceTypeDesc":62,"sizeBytes":63,"sourceHtmlUrl":64},"OWNERSHIP","OTHER","其他",12,"https:\u002F\u002Fgithub.com\u002FJetBrains\u002Fintellij-community\u002Fblob\u002Fmaster\u002F.claude\u002Fskills\u002Ffix-project-leak-from-tc-report\u002FOWNERSHIP",{"path":66,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":69,"sourceHtmlUrl":70},"scripts\u002Fleak-loop.sh","SCRIPT","脚本",3930,"https:\u002F\u002Fgithub.com\u002FJetBrains\u002Fintellij-community\u002Fblob\u002Fmaster\u002F.claude\u002Fskills\u002Ffix-project-leak-from-tc-report\u002Fscripts\u002Fleak-loop.sh",{"path":72,"resourceType":67,"resourceTypeDesc":68,"sizeBytes":73,"sourceHtmlUrl":74},"scripts\u002Fsummarize-runs.sh",2805,"https:\u002F\u002Fgithub.com\u002FJetBrains\u002Fintellij-community\u002Fblob\u002Fmaster\u002F.claude\u002Fskills\u002Ffix-project-leak-from-tc-report\u002Fscripts\u002Fsummarize-runs.sh",[76],{"skillNo":77,"name":78,"nameZh":6,"description":79,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":80,"categories":81,"keywords":86,"publishedAt":31},"01M6ACMD8QGRT0Z5BPV11SKQK2","plugin-test","Build Docker test images and run SkyWalking Python plugin\u002Funit\u002Fe2e tests locally, mirroring the CI pipeline","https:\u002F\u002Fgithub.com\u002Fapache\u002Fskywalking-python\u002Fblob\u002Fmaster\u002F.claude\u002Fskills\u002Fplugin-test\u002FSKILL.md",[82,85],{"categoryNo":83,"categoryName":84,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[87,24,88,26,27,89,90,30],"test","plugin test","e2e tests","run"]