PyObservability 0.0.0a1__py3-none-any.whl → 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,68 @@
1
+ """Module for packaging."""
2
+
3
+ import sys
4
+
5
+ from pyobservability.main import start # noqa: F401
6
+ from pyobservability.version import __version__
7
+
8
+
9
+ def _cli() -> None:
10
+ """Starter function to invoke the observability UI via CLI commands.
11
+
12
+ **Flags**
13
+ - ``--version | -V``: Prints the version.
14
+ - ``--help | -H``: Prints the help section.
15
+ - ``--env | -E <path>``: Filepath to load environment variables.
16
+
17
+ **Commands**
18
+ ``start``: Initiates the PyObservability as a regular script.
19
+ """
20
+ assert sys.argv[0].endswith("pyobservability"), "Invalid commandline trigger!!"
21
+ options = {
22
+ "--version | -V": "Prints the version.",
23
+ "--help | -H": "Prints the help section.",
24
+ "--env | -E <path>": "Filepath to load environment variables.",
25
+ "start": "Initiates the PyObservability as a regular script.",
26
+ }
27
+ # weird way to increase spacing to keep all values monotonic
28
+ _longest_key = len(max(options.keys()))
29
+ _pretext = "\n\t* "
30
+ choices = _pretext + _pretext.join(
31
+ f"{k} {'·' * (_longest_key - len(k) + 8)}→ {v}".expandtabs() for k, v in options.items()
32
+ )
33
+ args = [arg.lower() for arg in sys.argv[1:]]
34
+ try:
35
+ assert len(args) > 1
36
+ except (IndexError, AttributeError, AssertionError):
37
+ print(f"Cannot proceed without a valid arbitrary command. Please choose from {choices}")
38
+ exit(1)
39
+ env_file = None
40
+ if any(arg in args for arg in ["version", "--version", "-v"]):
41
+ print(f"PyObservability: {__version__}")
42
+ exit(0)
43
+ elif any(arg in args for arg in ["help", "--help", "-h"]):
44
+ print(f"Usage: pyobservability [arbitrary-command]\nOptions (and corresponding behavior):{choices}")
45
+ exit(0)
46
+ elif any(arg in args for arg in ["env", "--env", "E", "-e"]):
47
+ extra_index = next(
48
+ (index for index, arg in enumerate(args) if arg in ["env", "--env", "E", "-e"]),
49
+ None,
50
+ )
51
+ try:
52
+ env_file = sys.argv[extra_index + 2]
53
+ except (IndexError, TypeError):
54
+ print("Cannot proceed without a valid extra environment file path.")
55
+ exit(1)
56
+ elif any(arg in args for arg in ("start",)):
57
+ pass
58
+ else:
59
+ print(f"Unknown Option: {sys.argv[1]}\nArbitrary commands must be one of {choices}")
60
+ exit(1)
61
+ if any(arg in args for arg in ("start",)):
62
+ start(env_file=env_file)
63
+ else:
64
+ print(
65
+ "Insufficient Arguments:\n\tNo command received to initiate the PyObservability. "
66
+ f"Please choose from {choices}"
67
+ )
68
+ exit(1)
@@ -0,0 +1,6 @@
1
+ from enum import StrEnum
2
+
3
+
4
+ class APIEndpoints(StrEnum):
5
+ root = "/"
6
+ ws = "/ws"
@@ -0,0 +1,125 @@
1
+ import json
2
+ import os
3
+ import pathlib
4
+ import socket
5
+ from typing import List
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field, HttpUrl, PositiveInt
9
+ from pydantic.aliases import AliasChoices
10
+ from pydantic_settings import BaseSettings
11
+
12
+
13
+ class PydanticEnvConfig(BaseSettings):
14
+ """Pydantic BaseSettings with custom order for loading environment variables.
15
+
16
+ >>> PydanticEnvConfig
17
+
18
+ """
19
+
20
+ @classmethod
21
+ def settings_customise_sources(
22
+ cls,
23
+ settings_cls,
24
+ init_settings,
25
+ env_settings,
26
+ dotenv_settings,
27
+ file_secret_settings,
28
+ ):
29
+ """Order: dotenv, env, init, secrets files."""
30
+ return dotenv_settings, env_settings, init_settings, file_secret_settings
31
+
32
+
33
+ class MonitorTarget(BaseModel):
34
+ name: str
35
+ base_url: HttpUrl
36
+ apikey: str
37
+
38
+
39
+ def alias_choices(variable: str) -> AliasChoices:
40
+ """Custom alias choices for environment variables for GitHub.
41
+
42
+ Args:
43
+ variable: Variable name.
44
+
45
+ Returns:
46
+ AliasChoices:
47
+ Returns the alias choices for the variable.
48
+ """
49
+ return AliasChoices(variable, f"MONITOR_{variable}")
50
+
51
+
52
+ class EnvConfig(PydanticEnvConfig):
53
+ """Configuration settings for the server.
54
+
55
+ >>> EnvConfig
56
+
57
+ """
58
+
59
+ host: str = Field(socket.gethostbyname("localhost") or "0.0.0.0", validation_alias=alias_choices("HOST"))
60
+ port: PositiveInt = Field(8080, validation_alias=alias_choices("PORT"))
61
+
62
+ targets: List[MonitorTarget] = Field(..., validation_alias=alias_choices("TARGETS"))
63
+ interval: PositiveInt = Field(3, validation_alias=alias_choices("INTERVAL"))
64
+
65
+ username: str | None = Field(None, validation_alias=alias_choices("USERNAME"))
66
+ password: str | None = Field(None, validation_alias=alias_choices("PASSWORD"))
67
+
68
+ class Config:
69
+ """Environment variables configuration."""
70
+
71
+ env_prefix = ""
72
+ extra = "forbid"
73
+
74
+ @classmethod
75
+ def from_env_file(cls, filename: pathlib.Path) -> "EnvConfig":
76
+ """Create an instance of EnvConfig from environment file.
77
+
78
+ Args:
79
+ filename: Name of the env file.
80
+
81
+ Returns:
82
+ EnvConfig:
83
+ Loads the ``EnvConfig`` model.
84
+ """
85
+ # noinspection PyArgumentList
86
+ return cls(_env_file=filename)
87
+
88
+
89
+ def env_loader(**kwargs) -> EnvConfig:
90
+ """Loads environment variables based on filetypes or kwargs.
91
+
92
+ Returns:
93
+ EnvConfig:
94
+ Returns a reference to the ``EnvConfig`` object.
95
+ """
96
+ # Default to .env if no kwargs were passed
97
+ if not kwargs:
98
+ return EnvConfig.from_env_file(".env")
99
+ # Look for the kwarg env_file and process accordingly
100
+ if env_file := kwargs.get("env_file") or os.getenv("env_file"):
101
+ env_file = pathlib.Path(env_file)
102
+ assert env_file.is_file(), f"\n\tenv_file: [{env_file.resolve()!r}] does not exist"
103
+ if env_file.suffix.lower() == ".json":
104
+ with env_file.open() as stream:
105
+ env_data = json.load(stream)
106
+ return EnvConfig(**{k.lower(): v for k, v in env_data.items()})
107
+ if env_file.suffix.lower() in (".yaml", ".yml"):
108
+ with env_file.open() as stream:
109
+ env_data = yaml.load(stream, yaml.FullLoader)
110
+ return EnvConfig(**{k.lower(): v for k, v in env_data.items()})
111
+ if not env_file.suffix or env_file.suffix.lower() in (
112
+ ".text",
113
+ ".txt",
114
+ ".env",
115
+ "",
116
+ ):
117
+ return EnvConfig.from_env_file(env_file)
118
+ raise ValueError(
119
+ f"\n\tUnsupported format for {env_file!r}, " "can be one of (.json, .yaml, .yml, .txt, .text, .env)"
120
+ )
121
+ # Load env config with regular kwargs
122
+ return EnvConfig(**kwargs)
123
+
124
+
125
+ env: EnvConfig
pyobservability/main.py CHANGED
@@ -1,45 +1,47 @@
1
1
  import logging
2
- import os
3
2
  import pathlib
3
+ import warnings
4
4
 
5
- import dotenv
5
+ import uiauth
6
+ import uvicorn
6
7
  from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
8
+ from fastapi.routing import APIRoute, APIWebSocketRoute
7
9
  from fastapi.staticfiles import StaticFiles
8
10
  from fastapi.templating import Jinja2Templates
9
11
 
12
+ from pyobservability.config import enums, settings
10
13
  from pyobservability.monitor import Monitor
11
14
 
12
- dotenv.load_dotenv()
13
-
14
15
  LOGGER = logging.getLogger("uvicorn.default")
15
16
 
16
- app = FastAPI(title="Monitor UI")
17
+ PyObservability = FastAPI(title="PyObservability")
18
+ PyObservability.__name__ = "PyObservability"
19
+ PyObservability.description = "Observability page for nodes running PyNinja"
20
+
17
21
  root = pathlib.Path(__file__).parent
18
22
  templates_dir = root / "templates"
19
- static_dir = root / "static"
20
23
  templates = Jinja2Templates(directory=templates_dir)
21
- app.mount("/static", StaticFiles(directory=static_dir), name="static")
22
24
 
23
- monitor = Monitor(poll_interval=float(os.getenv("POLL_INTERVAL", 2)))
25
+ static_dir = root / "static"
26
+ PyObservability.mount("/static", StaticFiles(directory=static_dir), name="static")
24
27
 
25
28
 
26
- @app.get("/")
27
29
  async def index(request: Request):
28
30
  """Pass configured targets to the template so frontend can prebuild UI.
29
31
 
30
32
  Args:
31
33
  request: FastAPI request object.
32
34
  """
33
- return templates.TemplateResponse("index.html", {"request": request, "targets": monitor.targets})
35
+ return templates.TemplateResponse("index.html", {"request": request, "targets": settings.env.monitor_targets})
34
36
 
35
37
 
36
- @app.websocket("/ws")
37
38
  async def websocket_endpoint(websocket: WebSocket):
38
39
  """Websocket endpoint to render the metrics.
39
40
 
40
41
  Args:
41
42
  websocket: FastAPI websocket object.
42
43
  """
44
+ monitor = Monitor(targets=settings.env.monitor_targets, poll_interval=settings.env.poll_interval)
43
45
  await websocket.accept()
44
46
  await monitor.start()
45
47
  q = monitor.subscribe()
@@ -56,5 +58,55 @@ async def websocket_endpoint(websocket: WebSocket):
56
58
  await websocket.close()
57
59
  except Exception as err:
58
60
  LOGGER.warning(err)
59
- pass
60
61
  await monitor.stop()
62
+
63
+
64
+ def include_routes():
65
+ if all((settings.env.username, settings.env.password)):
66
+ uiauth.protect(
67
+ app=PyObservability,
68
+ username=settings.env.username,
69
+ password=settings.env.password,
70
+ params=[
71
+ uiauth.Parameters(
72
+ path=enums.APIEndpoints.root,
73
+ function=index,
74
+ methods=["GET"],
75
+ ),
76
+ uiauth.Parameters(
77
+ path=enums.APIEndpoints.ws,
78
+ function=websocket_endpoint,
79
+ route=APIWebSocketRoute,
80
+ ),
81
+ ],
82
+ )
83
+ else:
84
+ warnings.warn("\n\tRunning PyObservability without any protection.", UserWarning)
85
+ PyObservability.routes.append(
86
+ APIRoute(
87
+ path=enums.APIEndpoints.root,
88
+ endpoint=index,
89
+ methods=["GET"],
90
+ include_in_schema=False,
91
+ ),
92
+ )
93
+ PyObservability.routes.append(
94
+ APIWebSocketRoute(
95
+ path=enums.APIEndpoints.ws,
96
+ endpoint=websocket_endpoint,
97
+ )
98
+ )
99
+
100
+
101
+ def start(**kwargs):
102
+ settings.env = settings.env_loader(**kwargs)
103
+ settings.env.monitor_targets = [
104
+ {k: str(v) for k, v in target.model_dump().items()} for target in settings.env.monitor_targets
105
+ ]
106
+ include_routes()
107
+ uvicorn_args = dict(
108
+ host=settings.env.host,
109
+ port=settings.env.port,
110
+ app=PyObservability,
111
+ )
112
+ uvicorn.run(**uvicorn_args)
@@ -1,9 +1,7 @@
1
1
  # app/monitor.py
2
2
 
3
3
  import asyncio
4
- import json
5
4
  import logging
6
- import os
7
5
  from asyncio import CancelledError
8
6
  from typing import Any, Dict, List
9
7
  from urllib.parse import urlparse
@@ -60,42 +58,6 @@ ENDPOINTS = {
60
58
  }
61
59
 
62
60
 
63
- ###############################################################################
64
- # LOAD TARGETS FROM ENV
65
- ###############################################################################
66
-
67
-
68
- def load_targets_from_env() -> List[Dict[str, Any]]:
69
- """Loads monitor targets from environment variables and parses it.
70
-
71
- Returns:
72
- List[Dict[str, Any]]:
73
- Returns the parsed list of the monitor targets.
74
- """
75
- raw = os.getenv("MONITOR_TARGETS", "[]")
76
-
77
- try:
78
- data = json.loads(raw)
79
- except Exception:
80
- data = [raw] if raw else []
81
-
82
- parsed = []
83
-
84
- for entry in data:
85
- if isinstance(entry, str):
86
- parsed.append({"name": entry, "base_url": entry, "apikey": None})
87
- elif isinstance(entry, dict):
88
- parsed.append(
89
- {
90
- "name": entry.get("name") or entry["base_url"],
91
- "base_url": entry["base_url"],
92
- "apikey": entry.get("apikey"),
93
- }
94
- )
95
-
96
- return parsed
97
-
98
-
99
61
  ###############################################################################
100
62
  # MONITOR CLASS
101
63
  ###############################################################################
@@ -103,9 +65,9 @@ def load_targets_from_env() -> List[Dict[str, Any]]:
103
65
 
104
66
  class Monitor:
105
67
 
106
- def __init__(self, poll_interval: float = 2.0):
107
- self.targets = load_targets_from_env()
108
- self.poll_interval = float(os.getenv("POLL_INTERVAL", poll_interval))
68
+ def __init__(self, targets: List[Dict[str, str]], poll_interval: float):
69
+ self.targets = targets
70
+ self.poll_interval = poll_interval
109
71
  self.sessions: Dict[str, aiohttp.ClientSession] = {}
110
72
  self._ws_subscribers: List[asyncio.Queue] = []
111
73
  self._task = None
@@ -115,8 +77,8 @@ class Monitor:
115
77
  # LIFECYCLE
116
78
  ############################################################################
117
79
  async def start(self):
118
- for t in self.targets:
119
- self.sessions[t["base_url"]] = aiohttp.ClientSession()
80
+ for target in self.targets:
81
+ self.sessions[target["base_url"]] = aiohttp.ClientSession()
120
82
  self._task = asyncio.create_task(self._run_loop())
121
83
 
122
84
  async def stop(self):
@@ -148,12 +110,8 @@ class Monitor:
148
110
  ############################################################################
149
111
  # FETCH WRAPPER
150
112
  ############################################################################
151
- async def _fetch(self, session, base_url, ep, apikey=None, params=None):
113
+ async def _fetch(self, session, base_url, ep, headers: Dict[str, str], params=None):
152
114
  url = base_url.rstrip("/") + ep
153
- headers = {"accept": "application/json"}
154
- if apikey:
155
- headers["Authorization"] = f"Bearer {apikey}"
156
-
157
115
  try:
158
116
  async with session.get(url, headers=headers, params=params, timeout=10) as resp:
159
117
  if resp.status == 200:
@@ -174,8 +132,9 @@ class Monitor:
174
132
  ############################################################################
175
133
  async def _poll_target(self, target: Dict[str, Any]) -> Dict[str, Any]:
176
134
  base = target["base_url"]
177
- apikey = target.get("apikey")
135
+ apikey = target["apikey"]
178
136
  session = self.sessions[base]
137
+ headers = {"Accept": "application/json", "Authorization": f"Bearer {apikey}"}
179
138
 
180
139
  result = {"name": target["name"], "base_url": base, "metrics": {}}
181
140
 
@@ -184,7 +143,7 @@ class Monitor:
184
143
 
185
144
  for key, cfg in ENDPOINTS.items():
186
145
  tasks[key] = asyncio.create_task(
187
- self._fetch(session, base, cfg["path"], apikey=apikey, params=cfg["params"])
146
+ self._fetch(session, base, cfg["path"], headers=headers, params=cfg["params"])
188
147
  )
189
148
 
190
149
  # Wait for all endpoints
@@ -206,7 +165,7 @@ class Monitor:
206
165
  # POLL ALL HOSTS
207
166
  ############################################################################
208
167
  async def _poll_all(self) -> List[Dict[str, Any]]:
209
- tasks = [self._poll_target(t) for t in self.targets]
168
+ tasks = [self._poll_target(target) for target in self.targets]
210
169
  results = await asyncio.gather(*tasks, return_exceptions=True)
211
170
  out = []
212
171
  for r in results:
@@ -1 +1 @@
1
- __version__ = "0.0.0a1"
1
+ __version__ = "0.0.1"
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyObservability
3
+ Version: 0.0.1
4
+ Summary: Lightweight OS-agnostic observability UI for PyNinja
5
+ Author-email: Vignesh Rao <svignesh1793@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 TheVickypedia
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/thevickypedia/PyObservability
29
+ Project-URL: Docs, https://thevickypedia.github.io/PyObservability
30
+ Project-URL: Source, https://github.com/thevickypedia/PyObservability
31
+ Project-URL: Bug Tracker, https://github.com/thevickypedia/PyObservability/issues
32
+ Project-URL: Release Notes, https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
33
+ Keywords: PyObservability,observability,system-monitor,PyNinja
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Development Status :: 3 - Alpha
37
+ Classifier: Operating System :: MacOS :: MacOS X
38
+ Classifier: Operating System :: Microsoft :: Windows
39
+ Classifier: Operating System :: POSIX :: Linux
40
+ Classifier: Topic :: System :: Monitoring
41
+ Requires-Python: >=3.11
42
+ Description-Content-Type: text/markdown
43
+ License-File: LICENSE
44
+ Requires-Dist: aiohttp==3.13.*
45
+ Requires-Dist: fastapi==0.122.*
46
+ Requires-Dist: FastAPI-UI-Auth==0.2.1
47
+ Requires-Dist: Jinja2==3.1.*
48
+ Requires-Dist: pydantic==2.12.*
49
+ Requires-Dist: pydantic-settings==2.12.*
50
+ Requires-Dist: python-dotenv==1.2.*
51
+ Requires-Dist: uvicorn[standard]==0.38.*
52
+ Provides-Extra: dev
53
+ Requires-Dist: sphinx==5.1.1; extra == "dev"
54
+ Requires-Dist: pre-commit; extra == "dev"
55
+ Requires-Dist: recommonmark; extra == "dev"
56
+ Requires-Dist: gitverse; extra == "dev"
57
+ Dynamic: license-file
58
+
59
+ # PyObservability
60
+
61
+ ![Python][label-pyversion]
62
+
63
+ **Platform Supported**
64
+
65
+ ![Platform][label-platform]
66
+
67
+ **Deployments**
68
+
69
+ [![pypi][label-actions-pypi]][gha_pypi]
70
+ [![notes][label-actions-notes]][gha_notes]
71
+ [![release][label-actions-release]][gha_release]
72
+
73
+ [![Pypi][label-pypi]][pypi]
74
+ [![Pypi-format][label-pypi-format]][pypi-files]
75
+ [![Pypi-status][label-pypi-status]][pypi]
76
+
77
+ ## Kick off
78
+
79
+ **Recommendations**
80
+
81
+ - Install `python` [3.11] or above
82
+ - Use a dedicated [virtual environment]
83
+
84
+ **Install PyObservability**
85
+ ```shell
86
+ python -m pip install pyobservability
87
+ ```
88
+
89
+ **Initiate - IDE**
90
+ ```python
91
+ import pyobservability
92
+
93
+
94
+ if __name__ == '__main__':
95
+ pyobservability.start()
96
+ ```
97
+
98
+ **Initiate - CLI**
99
+ ```shell
100
+ pyobservability start
101
+ ```
102
+
103
+ > Use `pyobservability --help` for usage instructions.
104
+
105
+ ## Environment Variables
106
+
107
+ <details>
108
+ <summary><strong>Sourcing environment variables from an env file</strong></summary>
109
+
110
+ > _By default, `PyObservability` will look for a `.env` file in the current working directory._
111
+ > _Other file options are supported with a custom kwarg or env var `env_file` pointing to the filepath._
112
+ </details>
113
+
114
+ **Mandatory**
115
+ - **TARGETS** - Target URLs running `PyNinja` in the following format.
116
+ - `TARGETS='[{"name":"node1","base_url":"http://192.168.1.10:8000","apikey":"token1"},{"name":"node2","base_url":"http://192.168.1.11:8000"}]'`
117
+
118
+ **Defaults**
119
+ - **HOST** - Host IP to run PyObservability. Defaults to `127.0.0.1` or `0.0.0.0`
120
+ - **PORT** - Port number to run PyObservability. Defaults to `8080`
121
+ - **INTERVAL** - Polling interval to retrieve server information.
122
+
123
+ **Optional**
124
+ - **USERNAME** - Username to authenticate the monitoring page.
125
+ - **PASSWORD** - Password to authenticate the monitoring page.
126
+
127
+ ## License & copyright
128
+
129
+ &copy; Vignesh Rao
130
+
131
+ Licensed under the [MIT License][license]
132
+
133
+ [//]: # (Labels)
134
+
135
+ [label-pypi-package]: https://img.shields.io/badge/Pypi%20Package-pyobservability-blue?style=for-the-badge&logo=Python
136
+ [label-sphinx-doc]: https://img.shields.io/badge/Made%20with-Sphinx-blue?style=for-the-badge&logo=Sphinx
137
+ [label-pyversion]: https://img.shields.io/badge/python-3.11%20%7C%203.12-blue
138
+ [label-platform]: https://img.shields.io/badge/Platform-Linux|macOS|Windows-1f425f.svg
139
+ [label-actions-pypi]: https://github.com/thevickypedia/PyObservability/actions/workflows/python-publish.yml/badge.svg
140
+ [label-pypi]: https://img.shields.io/pypi/v/PyObservability
141
+ [label-pypi-format]: https://img.shields.io/pypi/format/PyObservability
142
+ [label-pypi-status]: https://img.shields.io/pypi/status/PyObservability
143
+ [label-actions-notes]: https://github.com/thevickypedia/PyObservability/actions/workflows/notes.yml/badge.svg
144
+ [label-actions-release]: https://github.com/thevickypedia/PyObservability/actions/workflows/release.yml/badge.svg
145
+
146
+ [3.11]: https://docs.python.org/3/whatsnew/3.11.html
147
+ [virtual environment]: https://docs.python.org/3/tutorial/venv.html
148
+ [release-notes]: https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
149
+ [gha_pypi]: https://github.com/thevickypedia/PyObservability/actions/workflows/python-publish.yml
150
+ [gha_notes]: https://github.com/thevickypedia/PyObservability/actions/workflows/notes.yml
151
+ [gha_release]: https://github.com/thevickypedia/PyObservability/actions/workflows/release.yml
152
+ [google-docs]: https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings
153
+ [pep8]: https://www.python.org/dev/peps/pep-0008/
154
+ [isort]: https://pycqa.github.io/isort/
155
+ [sphinx]: https://www.sphinx-doc.org/en/master/man/sphinx-autogen.html
156
+ [pypi]: https://pypi.org/project/PyObservability
157
+ [pypi-files]: https://pypi.org/project/PyObservability/#files
158
+ [pypi-repo]: https://packaging.python.org/tutorials/packaging-projects/
159
+ [license]: https://github.com/thevickypedia/PyObservability/blob/main/LICENSE
160
+ [runbook]: https://thevickypedia.github.io/PyObservability/
161
+ [samples]: https://github.com/thevickypedia/PyObservability/tree/main/samples
162
+ [PyUdisk]: https://github.com/thevickypedia/PyUdisk
163
+ [PyArchitecture]: https://github.com/thevickypedia/PyArchitecture
@@ -0,0 +1,15 @@
1
+ pyobservability/__init__.py,sha256=rr4udGMbbNPl3yo7l8R3FUUVVahBtYVaW6vSWWgXlv0,2617
2
+ pyobservability/main.py,sha256=5_c4Q2D5EgUTQgs6kULBpn0O8dF_k7LacdQhPq1qHnk,3451
3
+ pyobservability/monitor.py,sha256=s2sVp97sLjkkdtL6be82bX5ydu_gBdMSoWxDlmUtpgE,6613
4
+ pyobservability/version.py,sha256=sXLh7g3KC4QCFxcZGBTpG2scR7hmmBsMjq6LqRptkRg,22
5
+ pyobservability/config/enums.py,sha256=iMIOpa8LYSszkPIYBhupX--KrEXVTTsBurinpAxLvMA,86
6
+ pyobservability/config/settings.py,sha256=3nPvA-GsmtunUIGcroHqwgSvQ24TmIdINs-QRD_dg2s,3737
7
+ pyobservability/static/app.js,sha256=poc7eReoiRUbyI5JKnPwxSqmSNCuOge4aZKCITFy7eo,13494
8
+ pyobservability/static/styles.css,sha256=t6r1C0ueBanipgRRjdu18nmq6RbSGLK5bhpf0BdMOpQ,3245
9
+ pyobservability/templates/index.html,sha256=PsN3aq-7Q6RzGeshoNH5v37G3sHoI2saJq6mfuA6JYs,3977
10
+ pyobservability-0.0.1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
11
+ pyobservability-0.0.1.dist-info/METADATA,sha256=BQ4Z6EbkaRWOaY1oc4rf3wpulGoYtUhIfEPDBdCDYBU,6822
12
+ pyobservability-0.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ pyobservability-0.0.1.dist-info/entry_points.txt,sha256=DSGIr_VA8Tb3FYa2iNUYpf55eAvuFCAoInNS4ngXaME,57
14
+ pyobservability-0.0.1.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
15
+ pyobservability-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyobservability = pyobservability:_cli
@@ -1,56 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: PyObservability
3
- Version: 0.0.0a1
4
- Summary: Lightweight OS-agnostic observability UI for PyNinja
5
- Author-email: Vignesh Rao <svignesh1793@gmail.com>
6
- License: MIT License
7
-
8
- Copyright (c) 2025 TheVickypedia
9
-
10
- Permission is hereby granted, free of charge, to any person obtaining a copy
11
- of this software and associated documentation files (the "Software"), to deal
12
- in the Software without restriction, including without limitation the rights
13
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
- copies of the Software, and to permit persons to whom the Software is
15
- furnished to do so, subject to the following conditions:
16
-
17
- The above copyright notice and this permission notice shall be included in all
18
- copies or substantial portions of the Software.
19
-
20
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
- SOFTWARE.
27
-
28
- Project-URL: Homepage, https://github.com/thevickypedia/PyObservability
29
- Project-URL: Docs, https://thevickypedia.github.io/PyObservability
30
- Project-URL: Source, https://github.com/thevickypedia/PyObservability
31
- Project-URL: Bug Tracker, https://github.com/thevickypedia/PyObservability/issues
32
- Project-URL: Release Notes, https://github.com/thevickypedia/PyObservability/blob/main/release_notes.rst
33
- Keywords: PyObservability,observability,system-monitor,PyNinja
34
- Classifier: License :: OSI Approved :: MIT License
35
- Classifier: Programming Language :: Python :: 3
36
- Classifier: Development Status :: 3 - Alpha
37
- Classifier: Operating System :: MacOS :: MacOS X
38
- Classifier: Operating System :: Microsoft :: Windows
39
- Classifier: Operating System :: POSIX :: Linux
40
- Classifier: Topic :: System :: Monitoring
41
- Requires-Python: >=3.11
42
- Description-Content-Type: text/markdown
43
- License-File: LICENSE
44
- Requires-Dist: aiohttp
45
- Requires-Dist: fastapi
46
- Requires-Dist: jinja2
47
- Requires-Dist: python-dotenv
48
- Requires-Dist: uvicorn[standard]
49
- Provides-Extra: dev
50
- Requires-Dist: sphinx==5.1.1; extra == "dev"
51
- Requires-Dist: pre-commit; extra == "dev"
52
- Requires-Dist: recommonmark; extra == "dev"
53
- Requires-Dist: gitverse; extra == "dev"
54
- Dynamic: license-file
55
-
56
- # PyObservability
@@ -1,12 +0,0 @@
1
- pyobservability/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- pyobservability/main.py,sha256=XzH2XNuxU9lbFSnQXsJqJ2ytrd6nnzbTf7B0n0UQuW0,1637
3
- pyobservability/monitor.py,sha256=nnL6odqO3BF-XH1wek0leyMKXw7NB8qrwp6aAh-xJyA,7694
4
- pyobservability/version.py,sha256=PvT0JaYWREcshCnUiHC2CMpUMuTV359GmoET7cuhPiU,24
5
- pyobservability/static/app.js,sha256=poc7eReoiRUbyI5JKnPwxSqmSNCuOge4aZKCITFy7eo,13494
6
- pyobservability/static/styles.css,sha256=t6r1C0ueBanipgRRjdu18nmq6RbSGLK5bhpf0BdMOpQ,3245
7
- pyobservability/templates/index.html,sha256=PsN3aq-7Q6RzGeshoNH5v37G3sHoI2saJq6mfuA6JYs,3977
8
- pyobservability-0.0.0a1.dist-info/licenses/LICENSE,sha256=_sOIKJWdD2o1WwwDIwYB2qTP2nlSWqT5Tyg9jr1Xa4w,1070
9
- pyobservability-0.0.0a1.dist-info/METADATA,sha256=Ze1eq09JsbqSTcpPFIrvMQxKJubLqKxzAxcMhLIzbzw,2663
10
- pyobservability-0.0.0a1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
- pyobservability-0.0.0a1.dist-info/top_level.txt,sha256=p20T0EmihDYW1uMintRXr7X9bg3XWYKyoSbBHOVC1xI,16
12
- pyobservability-0.0.0a1.dist-info/RECORD,,