nnlogging 0.1.0__py3-none-any.whl → 0.1.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.

Potentially problematic release.


This version of nnlogging might be problematic. Click here for more details.

@@ -0,0 +1,154 @@
1
+ import logging
2
+ from collections.abc import Collection
3
+ from logging import Formatter as LoggingFormatter
4
+ from sys import stderr
5
+ from typing import Literal
6
+
7
+ from rich.console import Console as RichConsole
8
+ from rich.highlighter import (
9
+ Highlighter as RichHighlighter,
10
+ NullHighlighter as RichNullHighlighter,
11
+ )
12
+ from rich.logging import RichHandler
13
+ from rich.progress import Progress as RichProgress, ProgressColumn as RichProgressColumn
14
+ from rich.theme import Theme as RichTheme
15
+
16
+ from nnlogging.typings import FormatTimeCallable, Sink
17
+
18
+
19
+ def get_rich_console(
20
+ sink: Sink | None = None,
21
+ /,
22
+ *,
23
+ width: int | None = None,
24
+ height: int | None = None,
25
+ markup: bool = True,
26
+ emoji: bool = True,
27
+ color_system: Literal["auto", "standard", "truecolor"] | None = "auto",
28
+ theme: RichTheme | None = None,
29
+ highlighter: RichHighlighter | None = None,
30
+ soft_wrap: bool = True,
31
+ force_terminal: bool | None = None,
32
+ force_jupyter: bool | None = None,
33
+ force_interactive: bool | None = None,
34
+ ):
35
+ console = RichConsole(
36
+ file=sink or stderr, # pyright: ignore[reportArgumentType]
37
+ width=width,
38
+ height=height,
39
+ markup=markup,
40
+ emoji=emoji,
41
+ color_system=color_system,
42
+ theme=theme,
43
+ highlight=highlighter is not None
44
+ and not isinstance(highlighter, RichNullHighlighter),
45
+ highlighter=highlighter or RichNullHighlighter(),
46
+ soft_wrap=soft_wrap,
47
+ force_terminal=force_terminal,
48
+ force_jupyter=force_jupyter,
49
+ force_interactive=force_interactive,
50
+ )
51
+ return console
52
+
53
+
54
+ def get_rich_handler(
55
+ console: RichConsole | None = None,
56
+ /,
57
+ *,
58
+ level: str | int = logging.NOTSET,
59
+ show_level: bool = True,
60
+ show_time: bool = True,
61
+ show_path: bool = True,
62
+ log_time_format: str | FormatTimeCallable = "[%x %X]",
63
+ omit_repeated_times: bool = True,
64
+ markup: bool = True,
65
+ highlighter: RichHighlighter | None = None,
66
+ rich_tracebacks: bool = True,
67
+ tracebacks_show_locals: bool = False,
68
+ log_message_format: str | LoggingFormatter = "%(message)s",
69
+ ):
70
+ handler = RichHandler(
71
+ console=console or get_rich_console(),
72
+ level=level,
73
+ show_level=show_level,
74
+ show_time=show_time,
75
+ show_path=show_path,
76
+ log_time_format=log_time_format,
77
+ omit_repeated_times=omit_repeated_times,
78
+ markup=markup,
79
+ highlighter=highlighter or RichNullHighlighter(),
80
+ rich_tracebacks=rich_tracebacks,
81
+ tracebacks_show_locals=tracebacks_show_locals,
82
+ )
83
+ match log_message_format:
84
+ case str():
85
+ handler.setFormatter(LoggingFormatter(log_message_format))
86
+ case LoggingFormatter():
87
+ handler.setFormatter(log_message_format)
88
+ return handler
89
+
90
+
91
+ def get_rich_progress_default_columns(
92
+ *,
93
+ markup: bool = True,
94
+ ):
95
+ from rich.progress import (
96
+ BarColumn,
97
+ SpinnerColumn,
98
+ TaskProgressColumn,
99
+ TextColumn,
100
+ TimeRemainingColumn,
101
+ )
102
+
103
+ spinner_column = SpinnerColumn(
104
+ spinner_name="dots",
105
+ style="progress.spinner",
106
+ finished_text=" ",
107
+ )
108
+ text_column = TextColumn(
109
+ text_format="{task.description}",
110
+ style="progress.description",
111
+ justify="left",
112
+ markup=markup,
113
+ )
114
+ bar_column = BarColumn(bar_width=40)
115
+ task_progress_column = TaskProgressColumn(
116
+ text_format="{task.percentage:>3.0f}%",
117
+ text_format_no_percentage="",
118
+ style="progress.percentage",
119
+ justify="right",
120
+ markup=markup,
121
+ show_speed=True,
122
+ )
123
+ time_remaining_column = TimeRemainingColumn(
124
+ compact=False,
125
+ elapsed_when_finished=True,
126
+ )
127
+
128
+ return (
129
+ spinner_column,
130
+ text_column,
131
+ bar_column,
132
+ task_progress_column,
133
+ time_remaining_column,
134
+ )
135
+
136
+
137
+ def get_rich_progress(
138
+ console: RichConsole | None = None,
139
+ /,
140
+ *,
141
+ columns: Collection[str | RichProgressColumn] | None = None,
142
+ transient: bool = False,
143
+ refresh_per_second: float = 10,
144
+ speed_estimate_period: float = 3600,
145
+ default_column_markup: bool = True,
146
+ ):
147
+ progress = RichProgress(
148
+ *(columns or get_rich_progress_default_columns(markup=default_column_markup)),
149
+ console=console,
150
+ transient=transient,
151
+ refresh_per_second=refresh_per_second,
152
+ speed_estimate_period=speed_estimate_period,
153
+ )
154
+ return progress
@@ -0,0 +1,192 @@
1
+ import logging
2
+ from collections.abc import Collection
3
+ from dataclasses import asdict, dataclass, field
4
+ from logging import Formatter as LoggingFormatter
5
+ from typing import Literal
6
+
7
+ from aim import Repo as AimRepo, Run as AimRun
8
+ from rich.highlighter import Highlighter as RichHighlighter
9
+ from rich.progress import ProgressColumn as RichProgressColumn
10
+ from rich.theme import Theme as RichTheme
11
+
12
+ from nnlogging.typings import Branch, FormatTimeCallable, Omitable, Sink
13
+ from nnlogging.utils.helpers import evolve_, or_
14
+ from nnlogging.utils.rich_factories import (
15
+ get_rich_console,
16
+ get_rich_handler,
17
+ get_rich_progress_default_columns,
18
+ )
19
+
20
+
21
+ @dataclass(slots=True, kw_only=True)
22
+ class LoggerConfig:
23
+ name: str = "nnlogging"
24
+ level: int | str = logging.DEBUG
25
+ propagate: bool = False
26
+
27
+
28
+ def get_logging_logger(
29
+ config: LoggerConfig | None = None,
30
+ /,
31
+ *,
32
+ name: Omitable[str] = ...,
33
+ level: Omitable[int | str] = ...,
34
+ propagate: Omitable[bool] = ...,
35
+ ):
36
+ config_base = config or LoggerConfig()
37
+ config_evolved = evolve_(
38
+ config_base,
39
+ name=name,
40
+ level=level,
41
+ propagate=propagate,
42
+ )
43
+ logger = logging.getLogger(config_evolved.name)
44
+ logger.setLevel(config_evolved.level)
45
+ logger.propagate = config_evolved.propagate
46
+ return logger
47
+
48
+
49
+ @dataclass(slots=True, kw_only=True)
50
+ class RunConfig:
51
+ experiment: str | None = None
52
+ repo: str | AimRepo | None = None
53
+ system_tracking_interval: float | None = 10
54
+ capture_terminal_logs: bool = False
55
+ log_system_params: bool = False
56
+ run_hash: str | None = None
57
+ read_only: bool = False
58
+ force_resume: bool = False
59
+
60
+
61
+ def get_aim_run(
62
+ config: RunConfig | None = None,
63
+ /,
64
+ *,
65
+ experiment: Omitable[str | None] = ...,
66
+ repo: Omitable[str | AimRepo | None] = ...,
67
+ system_tracking_interval: Omitable[float | None] = ...,
68
+ capture_terminal_logs: Omitable[bool] = ...,
69
+ log_system_params: Omitable[bool] = ...,
70
+ run_hash: Omitable[str | None] = ...,
71
+ read_only: Omitable[bool] = ...,
72
+ force_resume: Omitable[bool] = ...,
73
+ ):
74
+ config_base = config or RunConfig()
75
+ config_evolved = evolve_(
76
+ config_base,
77
+ experiment=experiment,
78
+ repo=repo,
79
+ system_tracking_interval=system_tracking_interval,
80
+ capture_terminal_logs=capture_terminal_logs,
81
+ log_system_params=log_system_params,
82
+ run_hash=run_hash,
83
+ read_only=read_only,
84
+ force_resume=force_resume,
85
+ )
86
+ run = AimRun(**asdict(config_evolved)) # pyright: ignore[reportAny]
87
+ return run
88
+
89
+
90
+ @dataclass(slots=True, kw_only=True)
91
+ class BranchConfig:
92
+ # shared
93
+ markup: bool = True
94
+ highlighter: RichHighlighter | None = None
95
+
96
+ # console
97
+ width: int | None = None
98
+ height: int | None = None
99
+ emoji: bool = True
100
+ color_system: Literal["auto", "standard", "truecolor"] | None = "auto"
101
+ theme: RichTheme | None = None
102
+ soft_wrap: bool = True
103
+ force_terminal: bool | None = None
104
+ force_jupyter: bool | None = None
105
+ force_interactive: bool | None = None
106
+
107
+ # handler
108
+ level: str | int = logging.NOTSET
109
+ show_level: bool = True
110
+ show_time: bool = True
111
+ show_path: bool = True
112
+ log_time_format: str | FormatTimeCallable = "[%x %X]"
113
+ omit_repeated_times: bool = True
114
+ rich_tracebacks: bool = True
115
+ tracebacks_show_locals: bool = False
116
+ log_message_format: str | LoggingFormatter = "%(message)s"
117
+
118
+ # progress
119
+ columns: Collection[str | RichProgressColumn] = field(
120
+ default_factory=get_rich_progress_default_columns,
121
+ compare=False,
122
+ )
123
+ transient: bool = False
124
+ refresh_per_second: float = 10
125
+ speed_estimate_period: float = 3600
126
+ default_column_markup: bool = True
127
+
128
+
129
+ def get_branch(
130
+ config: BranchConfig | None = None,
131
+ /,
132
+ sink: Sink | None = None,
133
+ *,
134
+ markup: Omitable[bool] = ...,
135
+ highlighter: Omitable[RichHighlighter | None] = ...,
136
+ width: Omitable[int | None] = ...,
137
+ height: Omitable[int | None] = ...,
138
+ emoji: Omitable[bool] = ...,
139
+ color_system: Omitable[Literal["auto", "standard", "truecolor"] | None] = ...,
140
+ theme: Omitable[RichTheme | None] = ...,
141
+ soft_wrap: Omitable[bool] = ...,
142
+ force_terminal: Omitable[bool | None] = ...,
143
+ force_jupyter: Omitable[bool | None] = ...,
144
+ force_interactive: Omitable[bool | None] = ...,
145
+ level: Omitable[str | int] = ...,
146
+ show_level: Omitable[bool] = ...,
147
+ show_time: Omitable[bool] = ...,
148
+ show_path: Omitable[bool] = ...,
149
+ log_time_format: Omitable[str | FormatTimeCallable] = ...,
150
+ omit_repeated_times: Omitable[bool] = ...,
151
+ rich_tracebacks: Omitable[bool] = ...,
152
+ tracebacks_show_locals: Omitable[bool] = ...,
153
+ log_message_format: Omitable[str | LoggingFormatter] = ...,
154
+ ):
155
+ if config is None:
156
+ config = BranchConfig()
157
+ console = get_rich_console(
158
+ sink,
159
+ width=or_(width, config.width),
160
+ height=or_(height, config.height),
161
+ markup=or_(markup, config.markup),
162
+ emoji=or_(emoji, config.emoji),
163
+ color_system=or_(color_system, config.color_system),
164
+ theme=or_(theme, config.theme),
165
+ highlighter=or_(highlighter, config.highlighter),
166
+ soft_wrap=or_(soft_wrap, config.soft_wrap),
167
+ force_terminal=or_(force_terminal, config.force_terminal),
168
+ force_jupyter=or_(force_jupyter, config.force_jupyter),
169
+ force_interactive=or_(force_interactive, config.force_interactive),
170
+ )
171
+ handler = get_rich_handler(
172
+ console,
173
+ level=or_(level, config.level),
174
+ show_level=or_(show_level, config.show_level),
175
+ show_time=or_(show_time, config.show_time),
176
+ show_path=or_(show_path, config.show_path),
177
+ log_time_format=or_(log_time_format, config.log_time_format),
178
+ omit_repeated_times=or_(omit_repeated_times, config.omit_repeated_times),
179
+ markup=or_(markup, config.markup),
180
+ highlighter=or_(highlighter, config.highlighter),
181
+ rich_tracebacks=or_(rich_tracebacks, config.rich_tracebacks),
182
+ tracebacks_show_locals=or_(
183
+ tracebacks_show_locals, config.tracebacks_show_locals
184
+ ),
185
+ log_message_format=or_(log_message_format, config.log_message_format),
186
+ )
187
+ return Branch(
188
+ console=console,
189
+ handler=handler,
190
+ tasks=dict(),
191
+ progress=None,
192
+ )
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: nnlogging
3
+ Version: 0.1.1
4
+ Summary: A powerful and elegant logging library designed specifically for neural network and machine learning experiments. nnlogging seamlessly integrates [Rich](https://github.com/Textualize/rich) for beautiful terminal output and [Aim](https://github.com/aimhubio/aim) for comprehensive experiment tracking.
5
+ Requires-Python: <3.13,>=3.10
6
+ Requires-Dist: aim>=3.29.0
7
+ Requires-Dist: rich>=14.0.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: faker>=37.11.0; extra == 'dev'
10
+ Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
11
+ Requires-Dist: pytest-html>=4.1.1; extra == 'dev'
12
+ Requires-Dist: pytest-repeat>=0.9.4; extra == 'dev'
13
+ Requires-Dist: pytest-sugar>=1.1.1; extra == 'dev'
14
+ Requires-Dist: pytest-xdist>=3.8.0; extra == 'dev'
15
+ Requires-Dist: pytest>=8.4.2; extra == 'dev'
16
+ Requires-Dist: twine>=6.2.0; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # nnlogging
20
+
21
+ A powerful logging library for neural network and machine learning experiments
22
+ that combines [Rich](https://github.com/Textualize/rich) for beautiful terminal
23
+ output with [Aim](https://github.com/aimhubio/aim) for comprehensive experiment
24
+ tracking.
25
+
26
+ ## ✨ Features
27
+
28
+ - 🎨 **Beautiful Console Output** - Rich-powered colorful logging with progress
29
+ bars
30
+ - 📊 **Experiment Tracking** - Built-in Aim integration for metrics and
31
+ hyperparameters
32
+ - 🔧 **Flexible Logging** - Multiple console handlers with customizable
33
+ formatting
34
+ - 📈 **Progress Tracking** - Advanced progress bars for long-running training
35
+ loops
36
+ - 🎯 **ML-Focused Design** - Purpose-built for machine learning workflows
37
+ - 🐍 **Modern Python** - Python 3.10+ with full type hints
38
+
39
+ ## 🚀 Installation
40
+
41
+ ```bash
42
+ pip install nnlogging
43
+ ```
44
+
45
+ **Requirements:** Python 3.10-3.12
46
+
47
+ ## ⚡ Quick Start
48
+
49
+ ### Basic Logging
50
+
51
+ ```python
52
+ from nnlogging import Shell
53
+ from nnlogging.utils import get_rich_console
54
+ import sys
55
+
56
+ # Initialize logger
57
+ shell = Shell(name="my_experiment")
58
+ shell.add_console({"main": get_rich_console(sys.stderr)})
59
+ shell.build_handler_from_console()
60
+
61
+ # Log messages
62
+ shell.info("Starting training...")
63
+ shell.warn("Learning rate is high")
64
+ shell.error("CUDA out of memory")
65
+ ```
66
+
67
+ ### Experiment Tracking
68
+
69
+ ```python
70
+ # Initialize Aim tracking
71
+ shell.add_aimrun(experiment="resnet_training")
72
+
73
+ # Track metrics during training
74
+ for epoch in range(100):
75
+ train_loss, train_acc = train_epoch()
76
+ shell.track(train_loss, name="train_loss", epoch=epoch)
77
+ shell.track(train_acc, name="train_accuracy", epoch=epoch)
78
+
79
+ shell.info(f"Epoch {epoch}: Loss={train_loss:.4f}, Acc={train_acc:.3f}")
80
+
81
+ # Track hyperparameters
82
+ shell.update_aimrun_metadata("config", {
83
+ "learning_rate": 0.001,
84
+ "batch_size": 32,
85
+ "model": "ResNet50"
86
+ })
87
+ ```
88
+
89
+ ### Progress Tracking
90
+
91
+ ```python
92
+ # Setup progress bars
93
+ shell.build_progress_from_console("main")
94
+ shell.add_task("training", description="Training", total=1000)
95
+ shell.start_progress()
96
+
97
+ # Update during training loop
98
+ for step in range(1000):
99
+ loss = train_step()
100
+ shell.update_task("training", advance=1)
101
+
102
+ if step % 100 == 0:
103
+ shell.info(f"Step {step}: Loss={loss:.4f}")
104
+
105
+ shell.stop_progress()
106
+ ```
107
+
108
+ ### Complete Training Example
109
+
110
+ ```python
111
+ from nnlogging import Shell
112
+ from nnlogging.utils import get_rich_console
113
+ import sys
114
+
115
+ # Setup logging with both console and experiment tracking
116
+ shell = Shell(name="training")
117
+ shell.add_console({"console": get_rich_console(sys.stdout)})
118
+ shell.build_handler_from_console()
119
+ shell.build_progress_from_console()
120
+
121
+ # Initialize experiment tracking
122
+ shell.add_aimrun(
123
+ experiment="mnist_cnn",
124
+ log_system_params=True,
125
+ capture_terminal_logs=True
126
+ )
127
+
128
+ # Log hyperparameters
129
+ config = {"lr": 0.001, "batch_size": 64, "epochs": 10}
130
+ shell.update_aimrun_metadata("hparams", config)
131
+ shell.info(f"Starting training with config: {config}")
132
+
133
+ # Training loop with progress tracking
134
+ shell.add_task("epochs", description="Epochs", total=config["epochs"])
135
+ shell.start_progress()
136
+
137
+ for epoch in range(config["epochs"]):
138
+ # Training phase
139
+ train_loss, train_acc = train_model()
140
+ shell.track(train_loss, name="train_loss", epoch=epoch)
141
+ shell.track(train_acc, name="train_acc", epoch=epoch)
142
+
143
+ # Validation phase
144
+ val_loss, val_acc = validate_model()
145
+ shell.track(val_loss, name="val_loss", epoch=epoch)
146
+ shell.track(val_acc, name="val_acc", epoch=epoch)
147
+
148
+ shell.update_task("epochs", advance=1)
149
+ shell.info(f"Epoch {epoch}: Train Loss={train_loss:.3f}, Val Acc={val_acc:.3f}")
150
+
151
+ shell.stop_progress()
152
+ shell.info("Training completed! 🎉")
153
+ ```
154
+
155
+ ## 🔄 Workflow
156
+
157
+ ![Workflow Overview](assets/workflow-overview.png)
158
+
159
+ 1. **Initialize** - Create Shell instance for your experiment
160
+ 2. **Configure** - Add consoles and progress tracking
161
+ 3. **Track** - Connect to Aim for experiment tracking
162
+ 4. **Train & Log** - Use throughout your ML pipeline
163
+ 5. **Visualize** - View results in Aim's web interface with `aim up`
164
+
165
+ ## 🌍 Environments
166
+
167
+ nnlogging works seamlessly across different environments:
168
+
169
+ - **Local Development** - Full Rich terminal output with colors and formatting
170
+ - **Jupyter Notebooks** - Integrated display with notebook-friendly rendering
171
+ - **Remote Servers** - Automatic fallback for limited terminal capabilities
172
+ - **CI/CD Pipelines** - Clean text output when Rich features aren't supported
173
+ - **Docker Containers** - Optimized for containerized ML workloads
174
+
175
+ ## 🤝 Contributing
176
+
177
+ Contributions welcome! Please open an issue for major changes.
178
+
179
+ ## 📄 License
180
+
181
+ MIT License
@@ -0,0 +1,26 @@
1
+ nnlogging/__init__.py,sha256=sVUcI-0A0ID9W-l0FS9vNzoCngPnxQsDdMkXsFTsPOE,235
2
+ nnlogging/shell/__init__.py,sha256=tdcZDJuqlihYreih_jvgub0la_X8c3JWxnaNLR77u5c,128
3
+ nnlogging/shell/protocol.py,sha256=wVbdqBycfZqbj066HugmaCtQc9OdVxy47rvdLu5o5bc,1663
4
+ nnlogging/shell/shell.py,sha256=lN2aSzySnORGVI5DaljilfzDh7h-s9i4KqZs1mcH2rQ,11592
5
+ nnlogging/shell/exception/__init__.py,sha256=aFAbufsmio0Yndq6H7zEEoWGNA2kfO7F3ZL1p6iGYzQ,365
6
+ nnlogging/shell/exception/branch_exists.py,sha256=OpsZduL-vlOHn8RKmktw9J1Y-dlQI064zqJOb32wm_Y,1114
7
+ nnlogging/shell/exception/branch_not_found.py,sha256=r5RHg7DnP9VnraOJtioGi8BK7alhFLo7yKfaF13CD0g,1126
8
+ nnlogging/shell/exception/task_exists.py,sha256=m1ezYtxzdSR0MqFT2Qb1DkQUTIVMqt-AHN91punsP4g,1232
9
+ nnlogging/shell/exception/task_not_found.py,sha256=cydrhiRbnqP_ZvVMziF_iW7nY8lXUb7MnQgcIKfpxzo,1106
10
+ nnlogging/shell/utils/__init__.py,sha256=z70fg9IUfJOjXhJj3gi7U3RIJQGLWBfIV8vm5MkV_3w,675
11
+ nnlogging/shell/utils/branch_funcs.py,sha256=5UbPFDaHkfq9YRdMaGXW0bwwiufVQAUlkk-DnGe9abM,5559
12
+ nnlogging/shell/utils/logger_funcs.py,sha256=VdP7VpFQM_lwlFgdrkCJ9_tM90W9kX385LFNe-YCTGA,4611
13
+ nnlogging/shell/utils/run_funcs.py,sha256=9Q4SjftQIE4BCI_VzR2B1o5az27E37ey5yI0vBtVOpQ,2281
14
+ nnlogging/shell/utils/task_funcs.py,sha256=p76NvB-M5cNyTIqACDk7i_2qKNF1YOC6L_WDkt0qSeI,3222
15
+ nnlogging/typings/__init__.py,sha256=7ar5136odVB9vOj5MTQ6_AYuodoWOGduMSveEpVtF1Y,774
16
+ nnlogging/typings/aliases.py,sha256=EpLqeOss5u5-zpu-tbv6g-LZ21mFyKO0PpyXmDLrCUs,2080
17
+ nnlogging/typings/exceptions.py,sha256=rCmrwA599Uy7iOVnX12_Q8B0FMydyJX7zgOLBWxWDUM,174
18
+ nnlogging/typings/generics.py,sha256=olwknb77voWiIydEM62aMuU5kQPyS74zoyX7Ncr9cig,680
19
+ nnlogging/typings/protocols.py,sha256=osKmzyQ9Qzz_woxbd2eM9wDvPZ6hJZvupQ2cdkOM0H8,341
20
+ nnlogging/utils/__init__.py,sha256=cVr1eB99PAw_nSDlpMAopMGeQVI6Pouge5Bgkawo6fU,733
21
+ nnlogging/utils/helpers.py,sha256=8KTHeyQJR36Wnm8CRG7fZsx-IX6kXU4CsSkrTEgyZ0E,785
22
+ nnlogging/utils/rich_factories.py,sha256=RVI46Ncxk5aoMTbbQmf_mIB664IkYACXOiVOqwHLLt0,4473
23
+ nnlogging/utils/shell_factories.py,sha256=nGkQxhGZT19K4V_E94yqNTPlvnTGS245SrNYyc72B9o,6367
24
+ nnlogging-0.1.1.dist-info/METADATA,sha256=zUI3SPhYM9Q6pkEqx5vns07QuUmGrPBh_ryT6sA5k6w,5475
25
+ nnlogging-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ nnlogging-0.1.1.dist-info/RECORD,,