modular 0.1.0__tar.gz → 0.2.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Jeffrey Spies
3
+ Copyright (c) 2011-2025 Jeffrey Spies
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
modular-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: modular
3
+ Version: 0.2.0
4
+ Summary: A modular package for all sorts of things where you want choices
5
+ Author-email: Jeffrey Spies <code@jeffspies.com>
6
+ Project-URL: Homepage, https://github.com/221b-io/modular
7
+ Project-URL: Bug Tracker, https://github.com/221b-io/modular/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=7.0; extra == "test"
16
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # Modular
20
+
21
+ The code you will see here will be a set of two sorts:
22
+ - utilities to abstract core functionality (e.g., document/object storage) in such a way that said functionality can be referred to in a standardized, serializable way, while handling the implementation-specific stuff (e.g., MongoDB, postgres)--including versioning--as purely config that exists far-removed from the code
23
+ - some handy tools crafted in a standardized, serializable way, like the `tables` module below, that will also serve as examples of the approach
24
+
25
+ Why modular?
26
+ - It helps devs write clean, readable, configurable code.
27
+ - Actions are easily loggable--something taken for granted in highly asynchronous codebases.
28
+ - Because code is inherently configurable, environment management is much simpler.
29
+ - You can help avoid lock-in, either because you want to choose the best performing tool for the job and easily switch as needed or because e.g., pricing, license, priorities, or principles of a tool changes.
30
+
31
+ Use this a dependency injection (DI) framework? Yes and no. But yes.
32
+
33
+ ## History
34
+
35
+ I brought two of these into my dissertation project, which then became the flagship product of the non-profit I co-founded: the Open Science Framework. These were [`modular-odm`](https://github.com/cos-archives/modular-odm) and [`modular-file renderer`](https://github.com/CenterForOpenScience/modular-file-renderer). The latter is still used today; the former, while used heavily at COS, never reached its full potential. That may now change. The goal was to use abstraction to maximize choice and minimize lock-in.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install modular
41
+ ```
42
+
43
+ ## Features
44
+
45
+ ### Tables Module
46
+
47
+ The `tables` module provides a flexible system for normalizing and formatting tabular data. It consists of three main components:
48
+
49
+ 1. **Data Normalization** (`normalize_table`): Converts various input formats into a standardized table structure
50
+ 2. **Format Converters**: Built-in formatters for common output formats
51
+ 3. **High-level Table Function**: Combines normalization and formatting in one step
52
+
53
+ #### Supported Formats:
54
+ - Delimited (CSV, TSV, etc.)
55
+ - Markdown
56
+ - reStructuredText
57
+ - Custom formats through formatter functions
58
+
59
+ ## Usage
60
+
61
+ ### Basic Usage
62
+
63
+ The simplest way to create formatted tables is using the `table` function:
64
+
65
+ ```python
66
+ from modular.tables import table, markdown, delimited, rst
67
+
68
+ data = [
69
+ {"name": "Alice", "age": 30, "salary": 50000.123},
70
+ {"name": "Bob", "age": 25, "salary": 60000.456}
71
+ ]
72
+
73
+ # Convert to Markdown
74
+ md_table = table(data, formatter=markdown)
75
+
76
+ # Convert to CSV
77
+ csv_table = table(data, formatter=delimited)
78
+
79
+ # Convert to TSV
80
+ tsv_table = table(data, formatter=lambda t: delimited(t, delimiter='\t'))
81
+
82
+ # Convert to reStructuredText
83
+ rst_table = table(data, formatter=rst)
84
+ ```
85
+
86
+ ### Advanced Features
87
+
88
+ #### Column Ordering
89
+
90
+ Control the order of columns in the output:
91
+
92
+ ```python
93
+ md_table = table(
94
+ data,
95
+ formatter=markdown,
96
+ order=['name', 'city', 'age']
97
+ )
98
+ ```
99
+
100
+ #### Value Formatting
101
+
102
+ Apply format strings to specific columns or set a default format for all numeric values:
103
+
104
+ ```python
105
+ # Format specific columns
106
+ md_table = table(
107
+ data,
108
+ formatter=markdown,
109
+ cell_formats=['{:s}', '{:d}', '${:.2f}'] # string, integer, currency format
110
+ )
111
+
112
+ # Set default number format for all numeric values
113
+ md_table = table(
114
+ data,
115
+ formatter=markdown,
116
+ default_number_format='{:.3f}' # 3 decimal places for all numbers
117
+ )
118
+
119
+ # Combine both
120
+ md_table = table(
121
+ data,
122
+ formatter=markdown,
123
+ cell_formats=['{:s}', None, '${:.2f}'], # Use default_number_format where None
124
+ default_number_format='{:.3f}'
125
+ )
126
+ ```
127
+
128
+ #### Custom Headers
129
+
130
+ Specify custom column headers:
131
+
132
+ ```python
133
+ data = [
134
+ ['Alice', 30, 50000.123],
135
+ ['Bob', 25, 60000.456]
136
+ ]
137
+
138
+ md_table = table(
139
+ data,
140
+ formatter=markdown,
141
+ header=['Name', 'Age', 'Salary']
142
+ )
143
+ ```
144
+
145
+ #### Custom Formatters
146
+
147
+ Create your own formatters for custom output formats:
148
+
149
+ ```python
150
+ def html_formatter(table):
151
+ if not table['data']:
152
+ return ""
153
+
154
+ html = "<table>\n"
155
+ # Header row
156
+ html += " <tr>\n"
157
+ for header in table['header']:
158
+ html += f" <th>{header}</th>\n"
159
+ html += " </tr>\n"
160
+ # Data rows
161
+ for row in table['data']:
162
+ html += " <tr>\n"
163
+ for cell in row:
164
+ html += f" <td>{cell}</td>\n"
165
+ html += " </tr>\n"
166
+ html += "</table>"
167
+ return html
168
+
169
+ html_table = table(data, formatter=html_formatter)
170
+ ```
171
+
172
+ ### Low-level API
173
+
174
+ If you need more control, you can use the lower-level functions directly:
175
+
176
+ ```python
177
+ from modular.tables import normalize_table, markdown
178
+
179
+ # First normalize the data
180
+ normalized = normalize_table(
181
+ data,
182
+ header=['Name', 'Age', 'Salary'],
183
+ order=['Name', 'Salary', 'Age'],
184
+ cell_formats=['{:s}', '{:d}', '{:.2f}']
185
+ )
186
+
187
+ # Then convert to desired format
188
+ md_table = markdown(normalized)
189
+ ```
190
+
191
+ ## Development
192
+
193
+ To install the package in development mode:
194
+
195
+ ```bash
196
+ git clone https://github.com/yourusername/modular.git
197
+ cd modular
198
+ pip install -e .
199
+ ```
200
+
201
+ ### Running Tests
202
+
203
+ ```bash
204
+ # Run all tests
205
+ pytest tests/tables/
206
+
207
+ # Run specific test file
208
+ pytest tests/tables/test_table.py
209
+
210
+ # Run specific test class
211
+ pytest tests/tables/test_table.py::TestBuiltinFormatters
212
+ ```
213
+
214
+ ## License
215
+
216
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,198 @@
1
+ # Modular
2
+
3
+ The code you will see here will be a set of two sorts:
4
+ - utilities to abstract core functionality (e.g., document/object storage) in such a way that said functionality can be referred to in a standardized, serializable way, while handling the implementation-specific stuff (e.g., MongoDB, postgres)--including versioning--as purely config that exists far-removed from the code
5
+ - some handy tools crafted in a standardized, serializable way, like the `tables` module below, that will also serve as examples of the approach
6
+
7
+ Why modular?
8
+ - It helps devs write clean, readable, configurable code.
9
+ - Actions are easily loggable--something taken for granted in highly asynchronous codebases.
10
+ - Because code is inherently configurable, environment management is much simpler.
11
+ - You can help avoid lock-in, either because you want to choose the best performing tool for the job and easily switch as needed or because e.g., pricing, license, priorities, or principles of a tool changes.
12
+
13
+ Use this a dependency injection (DI) framework? Yes and no. But yes.
14
+
15
+ ## History
16
+
17
+ I brought two of these into my dissertation project, which then became the flagship product of the non-profit I co-founded: the Open Science Framework. These were [`modular-odm`](https://github.com/cos-archives/modular-odm) and [`modular-file renderer`](https://github.com/CenterForOpenScience/modular-file-renderer). The latter is still used today; the former, while used heavily at COS, never reached its full potential. That may now change. The goal was to use abstraction to maximize choice and minimize lock-in.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ pip install modular
23
+ ```
24
+
25
+ ## Features
26
+
27
+ ### Tables Module
28
+
29
+ The `tables` module provides a flexible system for normalizing and formatting tabular data. It consists of three main components:
30
+
31
+ 1. **Data Normalization** (`normalize_table`): Converts various input formats into a standardized table structure
32
+ 2. **Format Converters**: Built-in formatters for common output formats
33
+ 3. **High-level Table Function**: Combines normalization and formatting in one step
34
+
35
+ #### Supported Formats:
36
+ - Delimited (CSV, TSV, etc.)
37
+ - Markdown
38
+ - reStructuredText
39
+ - Custom formats through formatter functions
40
+
41
+ ## Usage
42
+
43
+ ### Basic Usage
44
+
45
+ The simplest way to create formatted tables is using the `table` function:
46
+
47
+ ```python
48
+ from modular.tables import table, markdown, delimited, rst
49
+
50
+ data = [
51
+ {"name": "Alice", "age": 30, "salary": 50000.123},
52
+ {"name": "Bob", "age": 25, "salary": 60000.456}
53
+ ]
54
+
55
+ # Convert to Markdown
56
+ md_table = table(data, formatter=markdown)
57
+
58
+ # Convert to CSV
59
+ csv_table = table(data, formatter=delimited)
60
+
61
+ # Convert to TSV
62
+ tsv_table = table(data, formatter=lambda t: delimited(t, delimiter='\t'))
63
+
64
+ # Convert to reStructuredText
65
+ rst_table = table(data, formatter=rst)
66
+ ```
67
+
68
+ ### Advanced Features
69
+
70
+ #### Column Ordering
71
+
72
+ Control the order of columns in the output:
73
+
74
+ ```python
75
+ md_table = table(
76
+ data,
77
+ formatter=markdown,
78
+ order=['name', 'city', 'age']
79
+ )
80
+ ```
81
+
82
+ #### Value Formatting
83
+
84
+ Apply format strings to specific columns or set a default format for all numeric values:
85
+
86
+ ```python
87
+ # Format specific columns
88
+ md_table = table(
89
+ data,
90
+ formatter=markdown,
91
+ cell_formats=['{:s}', '{:d}', '${:.2f}'] # string, integer, currency format
92
+ )
93
+
94
+ # Set default number format for all numeric values
95
+ md_table = table(
96
+ data,
97
+ formatter=markdown,
98
+ default_number_format='{:.3f}' # 3 decimal places for all numbers
99
+ )
100
+
101
+ # Combine both
102
+ md_table = table(
103
+ data,
104
+ formatter=markdown,
105
+ cell_formats=['{:s}', None, '${:.2f}'], # Use default_number_format where None
106
+ default_number_format='{:.3f}'
107
+ )
108
+ ```
109
+
110
+ #### Custom Headers
111
+
112
+ Specify custom column headers:
113
+
114
+ ```python
115
+ data = [
116
+ ['Alice', 30, 50000.123],
117
+ ['Bob', 25, 60000.456]
118
+ ]
119
+
120
+ md_table = table(
121
+ data,
122
+ formatter=markdown,
123
+ header=['Name', 'Age', 'Salary']
124
+ )
125
+ ```
126
+
127
+ #### Custom Formatters
128
+
129
+ Create your own formatters for custom output formats:
130
+
131
+ ```python
132
+ def html_formatter(table):
133
+ if not table['data']:
134
+ return ""
135
+
136
+ html = "<table>\n"
137
+ # Header row
138
+ html += " <tr>\n"
139
+ for header in table['header']:
140
+ html += f" <th>{header}</th>\n"
141
+ html += " </tr>\n"
142
+ # Data rows
143
+ for row in table['data']:
144
+ html += " <tr>\n"
145
+ for cell in row:
146
+ html += f" <td>{cell}</td>\n"
147
+ html += " </tr>\n"
148
+ html += "</table>"
149
+ return html
150
+
151
+ html_table = table(data, formatter=html_formatter)
152
+ ```
153
+
154
+ ### Low-level API
155
+
156
+ If you need more control, you can use the lower-level functions directly:
157
+
158
+ ```python
159
+ from modular.tables import normalize_table, markdown
160
+
161
+ # First normalize the data
162
+ normalized = normalize_table(
163
+ data,
164
+ header=['Name', 'Age', 'Salary'],
165
+ order=['Name', 'Salary', 'Age'],
166
+ cell_formats=['{:s}', '{:d}', '{:.2f}']
167
+ )
168
+
169
+ # Then convert to desired format
170
+ md_table = markdown(normalized)
171
+ ```
172
+
173
+ ## Development
174
+
175
+ To install the package in development mode:
176
+
177
+ ```bash
178
+ git clone https://github.com/yourusername/modular.git
179
+ cd modular
180
+ pip install -e .
181
+ ```
182
+
183
+ ### Running Tests
184
+
185
+ ```bash
186
+ # Run all tests
187
+ pytest tests/tables/
188
+
189
+ # Run specific test file
190
+ pytest tests/tables/test_table.py
191
+
192
+ # Run specific test class
193
+ pytest tests/tables/test_table.py::TestBuiltinFormatters
194
+ ```
195
+
196
+ ## License
197
+
198
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,5 @@
1
+ """
2
+ Modular package
3
+ """
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,9 @@
1
+ """
2
+ Table conversion utilities for various formats.
3
+ """
4
+
5
+ from .normalize import normalize_table
6
+ from .converters import delimited, markdown, rst
7
+ from .table import table
8
+
9
+ __all__ = ['table', 'normalize_table', 'delimited', 'markdown', 'rst']
@@ -0,0 +1,82 @@
1
+ """
2
+ Table conversion functions for various formats.
3
+ """
4
+
5
+ from typing import Dict, List, Any
6
+ import csv
7
+ from io import StringIO
8
+
9
+ def delimited(table: Dict[str, List], delimiter: str = ',') -> str:
10
+ """
11
+ Convert normalized table data to a delimited string format.
12
+
13
+ Args:
14
+ table: Dictionary with 'header' and 'data' keys from normalize_table
15
+ delimiter: Character to use as delimiter (default: ',')
16
+
17
+ Returns:
18
+ String containing the delimited data
19
+ """
20
+ if not table['data']:
21
+ return ""
22
+
23
+ output = StringIO(newline='') # Set newline='' to control line endings
24
+ writer = csv.writer(output, delimiter=delimiter, lineterminator='\n') # Explicitly set line endings
25
+ writer.writerow(table['header'])
26
+ writer.writerows(table['data'])
27
+ return output.getvalue()
28
+
29
+ def markdown(table: Dict[str, List]) -> str:
30
+ """
31
+ Convert normalized table data to Markdown table format.
32
+
33
+ Args:
34
+ table: Dictionary with 'header' and 'data' keys from normalize_table
35
+
36
+ Returns:
37
+ String containing the Markdown table
38
+ """
39
+ if not table['data']:
40
+ return ""
41
+
42
+ # Create header row
43
+ markdown = "| " + " | ".join(str(h) for h in table['header']) + " |\n"
44
+ # Create separator row
45
+ markdown += "| " + " | ".join(["---"] * len(table['header'])) + " |\n"
46
+ # Add data rows
47
+ for row in table['data']:
48
+ markdown += "| " + " | ".join(str(cell) for cell in row) + " |\n"
49
+
50
+ return markdown
51
+
52
+ def rst(table: Dict[str, List]) -> str:
53
+ """
54
+ Convert normalized table data to reStructuredText table format.
55
+
56
+ Args:
57
+ table: Dictionary with 'header' and 'data' keys from normalize_table
58
+
59
+ Returns:
60
+ String containing the reStructuredText table
61
+ """
62
+ if not table['data']:
63
+ return ""
64
+
65
+ # Calculate column widths
66
+ headers = [str(h) for h in table['header']]
67
+ data = [[str(cell) for cell in row] for row in table['data']]
68
+
69
+ col_widths = [max(len(str(cell)) for cell in col)
70
+ for col in zip(headers, *data)]
71
+
72
+ # Create header row
73
+ rst = "+" + "+".join("-" * (width + 2) for width in col_widths) + "+\n"
74
+ rst += "|" + "|".join(f" {header:<{width}} " for header, width in zip(headers, col_widths)) + "|\n"
75
+ rst += "+" + "+".join("=" * (width + 2) for width in col_widths) + "+\n"
76
+
77
+ # Add data rows
78
+ for row in data:
79
+ rst += "|" + "|".join(f" {cell:<{width}} " for cell, width in zip(row, col_widths)) + "|\n"
80
+ rst += "+" + "+".join("-" * (width + 2) for width in col_widths) + "+\n"
81
+
82
+ return rst
@@ -0,0 +1,104 @@
1
+ from typing import List, Dict, Union, Optional, Any
2
+ import numbers
3
+
4
+ def normalize_table(
5
+ data: Union[List[Dict[str, Any]], List[List[Any]]],
6
+ header: Optional[List[str]] = None,
7
+ order: Optional[List[str]] = None,
8
+ cell_formats: Optional[List[str]] = None,
9
+ default_number_format: str = "{:.2f}"
10
+ ) -> Dict[str, List]:
11
+ """
12
+ Normalize different data formats into a consistent table structure with formatting options.
13
+
14
+ Args:
15
+ data: List of dictionaries or list of lists containing the table data
16
+ header: Optional list of column names. If None and data is list of dicts,
17
+ will use dict keys as header
18
+ order: Optional list of column names/keys to specify column order
19
+ cell_formats: Optional list of format strings to apply to each column
20
+ default_number_format: Format string to apply to numeric values when no
21
+ cell_formats specified
22
+
23
+ Returns:
24
+ Dictionary with keys:
25
+ 'data': List of lists containing the normalized table data
26
+ 'header': List of column names
27
+ """
28
+ if not data:
29
+ return {'data': [], 'header': []}
30
+
31
+ # Handle list of dictionaries
32
+ if isinstance(data[0], dict):
33
+ if header is None:
34
+ # Use keys from first dictionary to establish initial order
35
+ header = list(data[0].keys())
36
+ # Add any additional keys from other dictionaries
37
+ for d in data[1:]:
38
+ for key in d.keys():
39
+ if key not in header:
40
+ header.append(key)
41
+
42
+ # Convert dicts to lists based on header order
43
+ table_data = [
44
+ [row.get(col, '') for col in header]
45
+ for row in data
46
+ ]
47
+
48
+ # Handle list of lists
49
+ else:
50
+ if header is None:
51
+ # Generate numeric headers
52
+ header = [str(i) for i in range(len(data[0]))]
53
+ table_data = list(data) # Make a copy to avoid modifying input
54
+
55
+ # Apply ordering if specified
56
+ if order:
57
+ # Create mapping of current positions
58
+ current_positions = {h: i for i, h in enumerate(header)}
59
+
60
+ # Create new header order
61
+ new_header = []
62
+ # First add specified columns in order
63
+ for h in order:
64
+ if h in header:
65
+ new_header.append(h)
66
+ # Then add any remaining columns
67
+ new_header.extend(h for h in header if h not in new_header)
68
+
69
+ # Create mapping for reordering data
70
+ reorder_indices = [current_positions[h] for h in new_header]
71
+
72
+ # Reorder the data
73
+ table_data = [
74
+ [row[i] for i in reorder_indices]
75
+ for row in table_data
76
+ ]
77
+
78
+ header = new_header
79
+
80
+ # Apply formatting
81
+ if cell_formats or default_number_format != "{:.2f}": # Apply if cell_formats provided or custom default_number_format
82
+ # Use cell_formats if provided, otherwise use default_number_format for all columns
83
+ formats = cell_formats if cell_formats else [default_number_format] * len(header)
84
+ # Ensure formats matches number of columns
85
+ formats = (formats + [default_number_format] * len(header))[:len(header)]
86
+
87
+ formatted_data = []
88
+ for row in table_data:
89
+ formatted_row = []
90
+ for value, fmt in zip(row, formats):
91
+ if isinstance(value, numbers.Number):
92
+ try:
93
+ formatted_row.append(fmt.format(value))
94
+ except ValueError:
95
+ formatted_row.append(default_number_format.format(value))
96
+ else:
97
+ formatted_row.append(str(value))
98
+ formatted_data.append(formatted_row)
99
+ table_data = formatted_data
100
+
101
+ return {
102
+ 'data': table_data,
103
+ 'header': header
104
+ }
@@ -0,0 +1,37 @@
1
+ from typing import List, Dict, Union, Optional, Any, Callable
2
+ import numbers
3
+ from .normalize import normalize_table
4
+
5
+ def table(
6
+ data: Union[List[Dict[str, Any]], List[List[Any]]],
7
+ formatter: Callable[[Dict[str, List]], str],
8
+ header: Optional[List[str]] = None,
9
+ order: Optional[List[str]] = None,
10
+ cell_formats: Optional[List[str]] = None,
11
+ default_number_format: str = "{:.2f}"
12
+ ) -> str:
13
+ """
14
+ Create a formatted table from input data.
15
+
16
+ Args:
17
+ data: List of dictionaries or list of lists containing the table data
18
+ formatter: Function that takes a normalized table dict and returns formatted string
19
+ header: Optional list of column names. If None and data is list of dicts,
20
+ will use dict keys as header
21
+ order: Optional list of column names/keys to specify column order
22
+ cell_formats: Optional list of format strings to apply to each column
23
+ default_number_format: Format string to apply to numeric values when no
24
+ cell_formats specified
25
+
26
+ Returns:
27
+ String containing the formatted table
28
+ """
29
+ normalized = normalize_table(
30
+ data=data,
31
+ header=header,
32
+ order=order,
33
+ cell_formats=cell_formats,
34
+ default_number_format=default_number_format
35
+ )
36
+
37
+ return formatter(normalized)
@@ -0,0 +1,216 @@
1
+ Metadata-Version: 2.4
2
+ Name: modular
3
+ Version: 0.2.0
4
+ Summary: A modular package for all sorts of things where you want choices
5
+ Author-email: Jeffrey Spies <code@jeffspies.com>
6
+ Project-URL: Homepage, https://github.com/221b-io/modular
7
+ Project-URL: Bug Tracker, https://github.com/221b-io/modular/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.7
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Provides-Extra: test
15
+ Requires-Dist: pytest>=7.0; extra == "test"
16
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
17
+ Dynamic: license-file
18
+
19
+ # Modular
20
+
21
+ The code you will see here will be a set of two sorts:
22
+ - utilities to abstract core functionality (e.g., document/object storage) in such a way that said functionality can be referred to in a standardized, serializable way, while handling the implementation-specific stuff (e.g., MongoDB, postgres)--including versioning--as purely config that exists far-removed from the code
23
+ - some handy tools crafted in a standardized, serializable way, like the `tables` module below, that will also serve as examples of the approach
24
+
25
+ Why modular?
26
+ - It helps devs write clean, readable, configurable code.
27
+ - Actions are easily loggable--something taken for granted in highly asynchronous codebases.
28
+ - Because code is inherently configurable, environment management is much simpler.
29
+ - You can help avoid lock-in, either because you want to choose the best performing tool for the job and easily switch as needed or because e.g., pricing, license, priorities, or principles of a tool changes.
30
+
31
+ Use this a dependency injection (DI) framework? Yes and no. But yes.
32
+
33
+ ## History
34
+
35
+ I brought two of these into my dissertation project, which then became the flagship product of the non-profit I co-founded: the Open Science Framework. These were [`modular-odm`](https://github.com/cos-archives/modular-odm) and [`modular-file renderer`](https://github.com/CenterForOpenScience/modular-file-renderer). The latter is still used today; the former, while used heavily at COS, never reached its full potential. That may now change. The goal was to use abstraction to maximize choice and minimize lock-in.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install modular
41
+ ```
42
+
43
+ ## Features
44
+
45
+ ### Tables Module
46
+
47
+ The `tables` module provides a flexible system for normalizing and formatting tabular data. It consists of three main components:
48
+
49
+ 1. **Data Normalization** (`normalize_table`): Converts various input formats into a standardized table structure
50
+ 2. **Format Converters**: Built-in formatters for common output formats
51
+ 3. **High-level Table Function**: Combines normalization and formatting in one step
52
+
53
+ #### Supported Formats:
54
+ - Delimited (CSV, TSV, etc.)
55
+ - Markdown
56
+ - reStructuredText
57
+ - Custom formats through formatter functions
58
+
59
+ ## Usage
60
+
61
+ ### Basic Usage
62
+
63
+ The simplest way to create formatted tables is using the `table` function:
64
+
65
+ ```python
66
+ from modular.tables import table, markdown, delimited, rst
67
+
68
+ data = [
69
+ {"name": "Alice", "age": 30, "salary": 50000.123},
70
+ {"name": "Bob", "age": 25, "salary": 60000.456}
71
+ ]
72
+
73
+ # Convert to Markdown
74
+ md_table = table(data, formatter=markdown)
75
+
76
+ # Convert to CSV
77
+ csv_table = table(data, formatter=delimited)
78
+
79
+ # Convert to TSV
80
+ tsv_table = table(data, formatter=lambda t: delimited(t, delimiter='\t'))
81
+
82
+ # Convert to reStructuredText
83
+ rst_table = table(data, formatter=rst)
84
+ ```
85
+
86
+ ### Advanced Features
87
+
88
+ #### Column Ordering
89
+
90
+ Control the order of columns in the output:
91
+
92
+ ```python
93
+ md_table = table(
94
+ data,
95
+ formatter=markdown,
96
+ order=['name', 'city', 'age']
97
+ )
98
+ ```
99
+
100
+ #### Value Formatting
101
+
102
+ Apply format strings to specific columns or set a default format for all numeric values:
103
+
104
+ ```python
105
+ # Format specific columns
106
+ md_table = table(
107
+ data,
108
+ formatter=markdown,
109
+ cell_formats=['{:s}', '{:d}', '${:.2f}'] # string, integer, currency format
110
+ )
111
+
112
+ # Set default number format for all numeric values
113
+ md_table = table(
114
+ data,
115
+ formatter=markdown,
116
+ default_number_format='{:.3f}' # 3 decimal places for all numbers
117
+ )
118
+
119
+ # Combine both
120
+ md_table = table(
121
+ data,
122
+ formatter=markdown,
123
+ cell_formats=['{:s}', None, '${:.2f}'], # Use default_number_format where None
124
+ default_number_format='{:.3f}'
125
+ )
126
+ ```
127
+
128
+ #### Custom Headers
129
+
130
+ Specify custom column headers:
131
+
132
+ ```python
133
+ data = [
134
+ ['Alice', 30, 50000.123],
135
+ ['Bob', 25, 60000.456]
136
+ ]
137
+
138
+ md_table = table(
139
+ data,
140
+ formatter=markdown,
141
+ header=['Name', 'Age', 'Salary']
142
+ )
143
+ ```
144
+
145
+ #### Custom Formatters
146
+
147
+ Create your own formatters for custom output formats:
148
+
149
+ ```python
150
+ def html_formatter(table):
151
+ if not table['data']:
152
+ return ""
153
+
154
+ html = "<table>\n"
155
+ # Header row
156
+ html += " <tr>\n"
157
+ for header in table['header']:
158
+ html += f" <th>{header}</th>\n"
159
+ html += " </tr>\n"
160
+ # Data rows
161
+ for row in table['data']:
162
+ html += " <tr>\n"
163
+ for cell in row:
164
+ html += f" <td>{cell}</td>\n"
165
+ html += " </tr>\n"
166
+ html += "</table>"
167
+ return html
168
+
169
+ html_table = table(data, formatter=html_formatter)
170
+ ```
171
+
172
+ ### Low-level API
173
+
174
+ If you need more control, you can use the lower-level functions directly:
175
+
176
+ ```python
177
+ from modular.tables import normalize_table, markdown
178
+
179
+ # First normalize the data
180
+ normalized = normalize_table(
181
+ data,
182
+ header=['Name', 'Age', 'Salary'],
183
+ order=['Name', 'Salary', 'Age'],
184
+ cell_formats=['{:s}', '{:d}', '{:.2f}']
185
+ )
186
+
187
+ # Then convert to desired format
188
+ md_table = markdown(normalized)
189
+ ```
190
+
191
+ ## Development
192
+
193
+ To install the package in development mode:
194
+
195
+ ```bash
196
+ git clone https://github.com/yourusername/modular.git
197
+ cd modular
198
+ pip install -e .
199
+ ```
200
+
201
+ ### Running Tests
202
+
203
+ ```bash
204
+ # Run all tests
205
+ pytest tests/tables/
206
+
207
+ # Run specific test file
208
+ pytest tests/tables/test_table.py
209
+
210
+ # Run specific test class
211
+ pytest tests/tables/test_table.py::TestBuiltinFormatters
212
+ ```
213
+
214
+ ## License
215
+
216
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -5,6 +5,9 @@ modular/__init__.py
5
5
  modular.egg-info/PKG-INFO
6
6
  modular.egg-info/SOURCES.txt
7
7
  modular.egg-info/dependency_links.txt
8
+ modular.egg-info/requires.txt
8
9
  modular.egg-info/top_level.txt
9
10
  modular/tables/__init__.py
10
- modular/tables/converters.py
11
+ modular/tables/converters.py
12
+ modular/tables/normalize.py
13
+ modular/tables/table.py
@@ -0,0 +1,4 @@
1
+
2
+ [test]
3
+ pytest>=7.0
4
+ pytest-cov>=4.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "modular"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  authors = [
9
9
  { name = "Jeffrey Spies", email = "code@jeffspies.com" },
10
10
  ]
@@ -18,5 +18,11 @@ classifiers = [
18
18
  ]
19
19
 
20
20
  [project.urls]
21
- "Homepage" = "https://github.com/yourusername/modular"
22
- "Bug Tracker" = "https://github.com/yourusername/modular/issues"
21
+ "Homepage" = "https://github.com/221b-io/modular"
22
+ "Bug Tracker" = "https://github.com/221b-io/modular/issues"
23
+
24
+ [project.optional-dependencies]
25
+ test = [
26
+ "pytest>=7.0",
27
+ "pytest-cov>=4.0",
28
+ ]
modular-0.1.0/PKG-INFO DELETED
@@ -1,92 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: modular
3
- Version: 0.1.0
4
- Summary: A modular package for all sorts of things where you want choices
5
- Author-email: Jeffrey Spies <code@jeffspies.com>
6
- Project-URL: Homepage, https://github.com/yourusername/modular
7
- Project-URL: Bug Tracker, https://github.com/yourusername/modular/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Dynamic: license-file
15
-
16
- # Modular
17
-
18
- A Python package for various data processing utilities with a focus on modularity and choice.
19
-
20
- ## Installation
21
-
22
- ```bash
23
- pip install modular
24
- ```
25
-
26
- ## Features
27
-
28
- ### Tables Module
29
-
30
- The `tables` module provides functions to convert data into various table formats.
31
-
32
- #### Supported Formats:
33
- - Delimited (CSV, TSV, etc.)
34
- - Markdown
35
- - reStructuredText
36
-
37
- ## Usage
38
-
39
- ### Converting Data to Different Formats
40
-
41
- ```python
42
- from modular.tables import to_delimited, to_markdown, to_rst
43
-
44
- # Example data as a list of dictionaries
45
- data_dict = [
46
- {"name": "Alice", "age": 30, "city": "New York"},
47
- {"name": "Bob", "age": 25, "city": "Los Angeles"},
48
- {"name": "Charlie", "age": 35, "city": "Chicago"}
49
- ]
50
-
51
- # Convert to CSV
52
- csv_output = to_delimited(data_dict)
53
- print(csv_output)
54
-
55
- # Convert to TSV
56
- tsv_output = to_delimited(data_dict, delimiter='\t')
57
- print(tsv_output)
58
-
59
- # Convert to Markdown
60
- md_output = to_markdown(data_dict)
61
- print(md_output)
62
-
63
- # Convert to reStructuredText
64
- rst_output = to_rst(data_dict)
65
- print(rst_output)
66
- ```
67
-
68
- You can also use lists of lists:
69
-
70
- ```python
71
- data_list = [
72
- ["Alice", 30, "New York"],
73
- ["Bob", 25, "Los Angeles"],
74
- ["Charlie", 35, "Chicago"]
75
- ]
76
-
77
- # Convert to any format as shown above
78
- ```
79
-
80
- ## Development
81
-
82
- To install the package in development mode:
83
-
84
- ```bash
85
- git clone https://github.com/yourusername/modular.git
86
- cd modular
87
- pip install -e .
88
- ```
89
-
90
- ## License
91
-
92
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
modular-0.1.0/README.md DELETED
@@ -1,77 +0,0 @@
1
- # Modular
2
-
3
- A Python package for various data processing utilities with a focus on modularity and choice.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pip install modular
9
- ```
10
-
11
- ## Features
12
-
13
- ### Tables Module
14
-
15
- The `tables` module provides functions to convert data into various table formats.
16
-
17
- #### Supported Formats:
18
- - Delimited (CSV, TSV, etc.)
19
- - Markdown
20
- - reStructuredText
21
-
22
- ## Usage
23
-
24
- ### Converting Data to Different Formats
25
-
26
- ```python
27
- from modular.tables import to_delimited, to_markdown, to_rst
28
-
29
- # Example data as a list of dictionaries
30
- data_dict = [
31
- {"name": "Alice", "age": 30, "city": "New York"},
32
- {"name": "Bob", "age": 25, "city": "Los Angeles"},
33
- {"name": "Charlie", "age": 35, "city": "Chicago"}
34
- ]
35
-
36
- # Convert to CSV
37
- csv_output = to_delimited(data_dict)
38
- print(csv_output)
39
-
40
- # Convert to TSV
41
- tsv_output = to_delimited(data_dict, delimiter='\t')
42
- print(tsv_output)
43
-
44
- # Convert to Markdown
45
- md_output = to_markdown(data_dict)
46
- print(md_output)
47
-
48
- # Convert to reStructuredText
49
- rst_output = to_rst(data_dict)
50
- print(rst_output)
51
- ```
52
-
53
- You can also use lists of lists:
54
-
55
- ```python
56
- data_list = [
57
- ["Alice", 30, "New York"],
58
- ["Bob", 25, "Los Angeles"],
59
- ["Charlie", 35, "Chicago"]
60
- ]
61
-
62
- # Convert to any format as shown above
63
- ```
64
-
65
- ## Development
66
-
67
- To install the package in development mode:
68
-
69
- ```bash
70
- git clone https://github.com/yourusername/modular.git
71
- cd modular
72
- pip install -e .
73
- ```
74
-
75
- ## License
76
-
77
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
@@ -1,5 +0,0 @@
1
- """
2
- Modular package for various data processing utilities.
3
- """
4
-
5
- __version__ = "0.1.0"
@@ -1,7 +0,0 @@
1
- """
2
- Table conversion utilities for various formats.
3
- """
4
-
5
- from .converters import to_delimited, to_markdown, to_rst
6
-
7
- __all__ = ['to_delimited', 'to_markdown', 'to_rst']
@@ -1,102 +0,0 @@
1
- """
2
- Table conversion functions for various formats.
3
- """
4
-
5
- from typing import Union, List, Dict, Any
6
- import csv
7
- from io import StringIO
8
-
9
- def _normalize_data(data: Union[List[List[Any]], List[Dict[str, Any]]]) -> tuple[List[List[str]], List[str]]:
10
- """
11
- Normalize input data to a consistent format.
12
-
13
- Args:
14
- data: List of lists or list of dictionaries
15
-
16
- Returns:
17
- Tuple of (rows, headers)
18
- """
19
- if not data:
20
- return [], []
21
-
22
- if isinstance(data[0], dict):
23
- headers = list(data[0].keys())
24
- rows = [[str(row.get(header, '')) for header in headers] for row in data]
25
- else:
26
- rows = [[str(cell) for cell in row] for row in data]
27
- headers = [f"Column {i+1}" for i in range(len(rows[0]))] if rows else []
28
-
29
- return rows, headers
30
-
31
- def to_delimited(data: Union[List[List[Any]], List[Dict[str, Any]]],
32
- delimiter: str = ',') -> str:
33
- """
34
- Convert data to a delimited string format.
35
-
36
- Args:
37
- data: List of lists or list of dictionaries
38
- delimiter: Character to use as delimiter (default: ',')
39
-
40
- Returns:
41
- String containing the delimited data
42
- """
43
- rows, headers = _normalize_data(data)
44
- output = StringIO()
45
- writer = csv.writer(output, delimiter=delimiter)
46
- writer.writerow(headers)
47
- writer.writerows(rows)
48
- return output.getvalue()
49
-
50
- def to_markdown(data: Union[List[List[Any]], List[Dict[str, Any]]]) -> str:
51
- """
52
- Convert data to Markdown table format.
53
-
54
- Args:
55
- data: List of lists or list of dictionaries
56
-
57
- Returns:
58
- String containing the Markdown table
59
- """
60
- rows, headers = _normalize_data(data)
61
- if not rows:
62
- return ""
63
-
64
- # Create header row
65
- markdown = "| " + " | ".join(headers) + " |\n"
66
- # Create separator row
67
- markdown += "| " + " | ".join(["---"] * len(headers)) + " |\n"
68
- # Add data rows
69
- for row in rows:
70
- markdown += "| " + " | ".join(row) + " |\n"
71
-
72
- return markdown
73
-
74
- def to_rst(data: Union[List[List[Any]], List[Dict[str, Any]]]) -> str:
75
- """
76
- Convert data to reStructuredText table format.
77
-
78
- Args:
79
- data: List of lists or list of dictionaries
80
-
81
- Returns:
82
- String containing the reStructuredText table
83
- """
84
- rows, headers = _normalize_data(data)
85
- if not rows:
86
- return ""
87
-
88
- # Calculate column widths
89
- col_widths = [max(len(str(cell)) for cell in col)
90
- for col in zip(headers, *rows)]
91
-
92
- # Create header row
93
- rst = "+" + "+".join("-" * (width + 2) for width in col_widths) + "+\n"
94
- rst += "|" + "|".join(f" {header:<{width}} " for header, width in zip(headers, col_widths)) + "|\n"
95
- rst += "+" + "+".join("=" * (width + 2) for width in col_widths) + "+\n"
96
-
97
- # Add data rows
98
- for row in rows:
99
- rst += "|" + "|".join(f" {cell:<{width}} " for cell, width in zip(row, col_widths)) + "|\n"
100
- rst += "+" + "+".join("-" * (width + 2) for width in col_widths) + "+\n"
101
-
102
- return rst
@@ -1,92 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: modular
3
- Version: 0.1.0
4
- Summary: A modular package for all sorts of things where you want choices
5
- Author-email: Jeffrey Spies <code@jeffspies.com>
6
- Project-URL: Homepage, https://github.com/yourusername/modular
7
- Project-URL: Bug Tracker, https://github.com/yourusername/modular/issues
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Operating System :: OS Independent
11
- Requires-Python: >=3.7
12
- Description-Content-Type: text/markdown
13
- License-File: LICENSE
14
- Dynamic: license-file
15
-
16
- # Modular
17
-
18
- A Python package for various data processing utilities with a focus on modularity and choice.
19
-
20
- ## Installation
21
-
22
- ```bash
23
- pip install modular
24
- ```
25
-
26
- ## Features
27
-
28
- ### Tables Module
29
-
30
- The `tables` module provides functions to convert data into various table formats.
31
-
32
- #### Supported Formats:
33
- - Delimited (CSV, TSV, etc.)
34
- - Markdown
35
- - reStructuredText
36
-
37
- ## Usage
38
-
39
- ### Converting Data to Different Formats
40
-
41
- ```python
42
- from modular.tables import to_delimited, to_markdown, to_rst
43
-
44
- # Example data as a list of dictionaries
45
- data_dict = [
46
- {"name": "Alice", "age": 30, "city": "New York"},
47
- {"name": "Bob", "age": 25, "city": "Los Angeles"},
48
- {"name": "Charlie", "age": 35, "city": "Chicago"}
49
- ]
50
-
51
- # Convert to CSV
52
- csv_output = to_delimited(data_dict)
53
- print(csv_output)
54
-
55
- # Convert to TSV
56
- tsv_output = to_delimited(data_dict, delimiter='\t')
57
- print(tsv_output)
58
-
59
- # Convert to Markdown
60
- md_output = to_markdown(data_dict)
61
- print(md_output)
62
-
63
- # Convert to reStructuredText
64
- rst_output = to_rst(data_dict)
65
- print(rst_output)
66
- ```
67
-
68
- You can also use lists of lists:
69
-
70
- ```python
71
- data_list = [
72
- ["Alice", 30, "New York"],
73
- ["Bob", 25, "Los Angeles"],
74
- ["Charlie", 35, "Chicago"]
75
- ]
76
-
77
- # Convert to any format as shown above
78
- ```
79
-
80
- ## Development
81
-
82
- To install the package in development mode:
83
-
84
- ```bash
85
- git clone https://github.com/yourusername/modular.git
86
- cd modular
87
- pip install -e .
88
- ```
89
-
90
- ## License
91
-
92
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
File without changes