From 966ea5a9ccaea76c383a9cedca2feadb1038ce7a Mon Sep 17 00:00:00 2001 From: ryanseq-gyg Date: Sun, 23 Nov 2025 14:45:50 +0100 Subject: [PATCH] docs: improved the API docs website --- .../expectations/aggregation/unique.py | 35 +- dataframe_expectations/suite.py | 6 +- dataframe_expectations/suite.pyi | 6 +- docs/requirements.txt | 2 +- docs/source/_ext/expectations_autodoc.py | 69 +- docs/source/_static/custom.css | 807 ++++++------------ docs/source/conf.py | 26 +- pyproject.toml | 2 +- scripts/generate_suite_stubs.py | 2 +- uv.lock | 26 +- 10 files changed, 382 insertions(+), 599 deletions(-) diff --git a/dataframe_expectations/expectations/aggregation/unique.py b/dataframe_expectations/expectations/aggregation/unique.py index 5233824..115c83d 100644 --- a/dataframe_expectations/expectations/aggregation/unique.py +++ b/dataframe_expectations/expectations/aggregation/unique.py @@ -24,33 +24,30 @@ class ExpectationUniqueRows(DataFrameAggregationExpectation): """ - Expectation that checks if there are no duplicate rows for the given column names. If columns list is empty, checks for duplicates across all columns. + Expectation that checks if there are no duplicate rows for the given column names. - For example: - For column_names ["col1", "col2"] + If columns list is empty, checks for duplicates across all columns. - Given the following DataFrame: + Example:: - | col1 | col2 | col3 | - |------|------|------| - | 1 | 10 | 100 | - | 2 | 20 | 100 | - | 3 | 30 | 100 | - | 1 | 20 | 100 | + For column_names ["col1", "col2"], given a DataFrame with columns col1, col2, col3: - All rows are unique for columns ["col1", "col2"] and there will be no violations. + - If all rows have unique combinations of col1 and col2 values, there are no violations + - If any rows have identical col1 AND col2 values, those are violations - For the same columns_names and the following DataFrame: + Example passing case (all unique):: - | col1 | col2 | col3 | - |------|------|------| - | 1 | 10 | 100 | - | 2 | 20 | 100 | - | 3 | 30 | 100 | - | 1 | 10 | 100 | + col1=1, col2=10 + col1=2, col2=20 + col1=3, col2=30 + col1=1, col2=20 # Different col2, so unique - There will be 1 violation because the first and last rows are duplicates for columns ["col1", "col2"]. + Example failing case (duplicate found):: + col1=1, col2=10 + col1=2, col2=20 + col1=3, col2=30 + col1=1, col2=10 # Duplicate of first row """ def __init__(self, column_names: List[str], tags: Optional[List[str]] = None): diff --git a/dataframe_expectations/suite.py b/dataframe_expectations/suite.py index d844b1d..581a9ab 100644 --- a/dataframe_expectations/suite.py +++ b/dataframe_expectations/suite.py @@ -374,7 +374,8 @@ def validate(self, func: Optional[Callable] = None, *, allow_none: bool = False) by the decorated function. If validation fails, it raises DataFrameExpectationsSuiteFailure. - Example: + Example:: + runner = suite.build() @runner.validate @@ -439,7 +440,8 @@ class DataFrameExpectationsSuite: Use this class to add expectations, then call build() to create an immutable runner that can execute the expectations on DataFrames. - Example: + Example:: + suite = DataFrameExpectationsSuite(suite_name="user_validation") suite.expect_value_greater_than( column_name="age", diff --git a/dataframe_expectations/suite.pyi b/dataframe_expectations/suite.pyi index e38c366..d30b14d 100644 --- a/dataframe_expectations/suite.pyi +++ b/dataframe_expectations/suite.pyi @@ -118,7 +118,8 @@ class DataFrameExpectationsSuiteRunner: by the decorated function. If validation fails, it raises DataFrameExpectationsSuiteFailure. - Example: + Example:: + runner = suite.build() @runner.validate @@ -149,7 +150,8 @@ class DataFrameExpectationsSuite: Use this class to add expectations, then call build() to create an immutable runner that can execute the expectations on DataFrames. - Example: + Example:: + suite = DataFrameExpectationsSuite(suite_name="user_validation") suite.expect_value_greater_than( column_name="age", diff --git a/docs/requirements.txt b/docs/requirements.txt index 5492f92..4b92744 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ sphinx>=4.0.0 -sphinx-rtd-theme>=1.0.0 +sphinx-book-theme>=1.0.0 sphinx-autobuild>=2021.3.14 diff --git a/docs/source/_ext/expectations_autodoc.py b/docs/source/_ext/expectations_autodoc.py index c79787f..76c4d65 100644 --- a/docs/source/_ext/expectations_autodoc.py +++ b/docs/source/_ext/expectations_autodoc.py @@ -168,7 +168,7 @@ def run(self) -> List[Node]: return nodes_list def _generate_summary_table(self, expectations_by_category, method_details) -> List[Node]: - """Generate summary table nodes.""" + """Generate summary as a two-level table""" nodes_list = [] # Add section with title and proper ID @@ -180,13 +180,17 @@ def _generate_summary_table(self, expectations_by_category, method_details) -> L # Create table table = nodes.table() + table['classes'] = ['summary-table'] tgroup = nodes.tgroup(cols=3) table += tgroup # Add column specifications - for width in [30, 25, 45]: - colspec = nodes.colspec(colwidth=width) - tgroup += colspec + colspec1 = nodes.colspec(colwidth=25) + colspec2 = nodes.colspec(colwidth=20) + colspec3 = nodes.colspec(colwidth=55) + tgroup += colspec1 + tgroup += colspec2 + tgroup += colspec3 # Add table head thead = nodes.thead() @@ -197,48 +201,67 @@ def _generate_summary_table(self, expectations_by_category, method_details) -> L for header in ["Category", "Subcategory", "Expectations"]: entry = nodes.entry() + entry['classes'] = ['summary-table-header'] row += entry - entry += nodes.paragraph("", header) + para = nodes.paragraph() + para += nodes.Text(header) + entry += para # Add table body tbody = nodes.tbody() tgroup += tbody for category in sorted(expectations_by_category.keys()): - for subcategory in sorted(expectations_by_category[category].keys()): - expectations = expectations_by_category[category][subcategory] + subcategories = expectations_by_category[category] + + for idx, subcategory in enumerate(sorted(subcategories.keys())): + expectations = subcategories[subcategory] row = nodes.row() + row['classes'] = ['summary-table-row'] tbody += row - # Category cell + # Category cell (only show on first subcategory) entry = nodes.entry() - row += entry - entry += nodes.paragraph("", category) + if idx == 0: + entry['morerows'] = len(subcategories) - 1 # Span multiple rows + entry['classes'] = ['summary-category-cell'] + para = nodes.paragraph() + para += nodes.Text(f"{category} ({sum(len(subcategories[s]) for s in subcategories)})") + entry += para + row += entry # Subcategory cell entry = nodes.entry() + entry['classes'] = ['summary-subcategory-cell'] + para = nodes.paragraph() + para += nodes.Text(f"{subcategory} ({len(expectations)})") + entry += para row += entry - entry += nodes.paragraph("", subcategory) - # Expectations cell + # Expectations cell with badges entry = nodes.entry() - row += entry + entry['classes'] = ['summary-expectations-cell'] + + badges_container = nodes.container() + badges_container['classes'] = ['expectation-badges'] - exp_para = nodes.paragraph() - for i, exp in enumerate(sorted(expectations)): - if i > 0: - exp_para += nodes.Text(", ") + for exp in sorted(expectations): + # Get description for tooltip + details = method_details[exp] + clean_docstring = clean_docstring_from_metadata(details["docstring"]) + description = clean_docstring.split('\n')[0] if clean_docstring else "" - # Create clickable link to the card using raw HTML - raw_link = nodes.raw( - f'{exp}', - f'{exp}', + # Create badge with link + badge = nodes.raw( + f'{exp}', + f'{exp}', format='html' ) - exp_para += raw_link + badges_container += badge - entry += exp_para + entry += badges_container + row += entry summary_section += table nodes_list.append(summary_section) diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css index 55f6815..4fa12b7 100644 --- a/docs/source/_static/custom.css +++ b/docs/source/_static/custom.css @@ -1,647 +1,392 @@ -/* Custom styling for expectations documentation with PyData Sphinx theme */ +/* Custom styling for DataFrame Expectations documentation */ -/* Fix PyData theme page width constraint that causes header overlapping */ -.bd-page-width { - max-width: none !important; - width: 100% !important; -} - -.bd-header__inner.bd-page-width { - max-width: none !important; - width: calc(100% - 2rem) !important; - margin: 0 1rem !important; -} - -/* Improve header layout to prevent overlapping */ -.bd-header .bd-header__inner { - justify-content: space-between !important; - align-items: center !important; - flex-wrap: nowrap !important; -} - -.col-lg-3.navbar-header-items__start { - flex: 0 0 auto !important; - max-width: 40% !important; - overflow: hidden !important; -} - -.col-lg-9.navbar-header-items { - flex: 1 1 auto !important; - min-width: 0 !important; - display: flex !important; - justify-content: space-between !important; - align-items: center !important; -} - -.navbar-brand.logo .title.logo__title { - font-size: 1rem !important; - white-space: nowrap !important; - overflow: hidden !important; - text-overflow: ellipsis !important; - max-width: 100% !important; -} - -.navbar-header-items__center { - flex: 1 1 auto !important; - min-width: 0 !important; - overflow: hidden !important; -} - -.navbar-header-items__end { - flex: 0 0 auto !important; -} - -/* Fix sidebar layout - left navigation with section nav underneath */ -.bd-sidebar-primary { - display: block !important; - width: 280px !important; - position: sticky !important; - top: var(--pst-header-height) !important; - height: calc(100vh - var(--pst-header-height)) !important; - overflow-y: auto !important; -} - -.bd-sidebar-secondary { - display: none !important; -} - -/* Ensure main content adjusts for left sidebar only */ -.bd-main { - display: flex !important; -} - -.bd-content { - flex: 1 !important; - min-width: 0 !important; - margin-left: 0 !important; - margin-right: 0 !important; -} - -/* Make sure the article container is properly sized */ -.bd-article-container { - width: 100% !important; - max-width: none !important; - padding: 0 3rem !important; - min-width: 0 !important; -} - -/* Style the "On this page" section in the left sidebar */ -.bd-sidebar-primary .bd-toc { - margin-top: 2rem !important; - padding-top: 1rem !important; - border-top: 1px solid var(--pst-color-border) !important; -} - -.bd-sidebar-primary .bd-toc .toc-title { - font-weight: bold !important; - margin-bottom: 0.75rem !important; - color: var(--pst-color-text-base) !important; - font-size: 0.9rem !important; - text-transform: uppercase !important; - letter-spacing: 0.5px !important; -} - -.bd-sidebar-primary .bd-toc ul { - list-style: none !important; - padding-left: 0 !important; - margin: 0 !important; -} - -.bd-sidebar-primary .bd-toc ul ul { - padding-left: 1rem !important; - margin-top: 0.25rem !important; +/* Expectation Gallery Cards - Theme-aware styling */ +.expectations-gallery { + margin: 2rem 0; + padding: 0; + width: 100%; +} + +/* Category and subcategory titles - scoped to expectations gallery */ +.expectations-gallery .category-title, +.expectations-gallery h1.category-title, +.expectations-gallery h2.category-title { + color: var(--pst-color-primary); + border-bottom: 3px solid var(--pst-color-primary); + padding-bottom: 10px; + margin: 2em 0 1em 0; + font-size: 1.4em; + font-weight: bold; } -.bd-sidebar-primary .bd-toc li { - margin-bottom: 0.25rem !important; +.expectations-gallery .subcategory-title { + color: var(--pst-color-text-base); + margin: 1.5em 0 1em 0; + font-size: 1.2em; + font-weight: bold; } -.bd-sidebar-primary .bd-toc a { - color: var(--pst-color-text-muted) !important; - text-decoration: none !important; - display: block !important; - padding: 0.25rem 0.5rem !important; - font-size: 0.85rem !important; - border-radius: 3px !important; - line-height: 1.4 !important; +/* Card grid layout */ +.expectations-gallery .cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); + gap: 16px; + margin: 1rem 0; + align-items: start; + padding: 0; + width: 100%; + max-width: none; } -.bd-sidebar-primary .bd-toc a:hover { - color: var(--pst-color-primary) !important; - background-color: var(--pst-color-primary-bg) !important; +/* Individual expectation card */ +.expectation-card { + border: 1px solid var(--pst-color-border); + border-radius: 6px; + background: var(--pst-color-code-bg, var(--pst-color-surface)); + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; + overflow: hidden; + min-height: 180px; + display: flex; + flex-direction: column; + margin: 0; + width: 100%; + min-width: 320px; } -.bd-sidebar-primary .bd-toc a.current { - color: var(--pst-color-primary) !important; - background-color: var(--pst-color-primary-bg) !important; - font-weight: 500 !important; +.expectation-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); + transform: translateY(-2px); + border-color: var(--pst-color-primary); } -/* Mobile responsive fixes */ -@media (max-width: 991px) { - .col-lg-3.navbar-header-items__start { - max-width: 50% !important; - } - - .navbar-brand.logo .title.logo__title { - font-size: 0.9rem !important; - } +/* Highlight card when navigated via anchor link */ +:target.expectation-card { + border: 1px solid var(--pst-color-primary) !important; + animation: highlight-pulse 5s ease-in-out; } -@media (max-width: 768px) { - .bd-header .bd-header__inner { - flex-wrap: wrap !important; - gap: 0.5rem !important; - } - - .col-lg-3.navbar-header-items__start, - .col-lg-9.navbar-header-items { - flex: 1 1 100% !important; - max-width: 100% !important; +@keyframes highlight-pulse { + 0% { + box-shadow: 0 0 0 0 rgba(255, 138, 0, 0.4); } - - .navbar-brand.logo .title.logo__title { - font-size: 1rem !important; - white-space: normal !important; - line-height: 1.2 !important; - } - - .bd-sidebar-primary { - width: 100% !important; - position: relative !important; - height: auto !important; + 10% { + box-shadow: 0 0 0 10px rgba(255, 138, 0, 0.3); } - - .bd-main { - flex-direction: column !important; - } -} - -/* Full-width layout for PyData theme */ -.bd-main .bd-content .bd-article-container { - max-width: none !important; -} - -.bd-container-fluid { - max-width: none !important; -} - -.bd-content { - padding-left: 3rem !important; - padding-right: 3rem !important; -} - -/* Style all tables in the expectations documentation */ -table.docutils { - width: 100% !important; - table-layout: fixed !important; - border-collapse: collapse !important; - margin: 1em 0 !important; -} - -table.docutils th, -table.docutils td { - border: 1px solid #ddd !important; - padding: 8px !important; - text-align: left !important; - vertical-align: top !important; - word-wrap: break-word !important; /* Allow long words to break */ - overflow-wrap: break-word !important; /* Modern browsers */ - white-space: normal !important; /* Allow text wrapping */ -} - -/* Set specific column widths for the expectations summary table */ -table.docutils th:nth-child(1), -table.docutils td:nth-child(1) { - width: 25% !important; /* Category column */ -} - -table.docutils th:nth-child(2), -table.docutils td:nth-child(2) { - width: 20% !important; /* Subcategory column */ -} - -table.docutils th:nth-child(3), -table.docutils td:nth-child(3) { - width: 55% !important; /* Expectations column */ -} - -table.docutils th { - background-color: #f5f5f5 !important; - font-weight: bold !important; -} - -table.docutils tr:nth-child(even) { - background-color: #f9f9f9 !important; -} - -/* Make expectation names in summary table clickable and styled */ -table.docutils td a, -table.docutils td a.expectation-link { - word-break: break-word !important; - display: inline !important; - color: #007bff !important; - text-decoration: none !important; - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace !important; - font-size: 0.9em !important; - padding: 2px 4px !important; - background-color: #f8f9fa !important; - border-radius: 3px !important; - border: 1px solid #e9ecef !important; -} - -table.docutils td a:hover, -table.docutils td a.expectation-link:hover { - background-color: #e3f2fd !important; - border-color: #2196f3 !important; - text-decoration: none !important; -} - -/* For very small screens, allow some responsiveness */ -@media (max-width: 768px) { - table.docutils { - font-size: 0.9em !important; + 50% { + box-shadow: 0 0 0 8px rgba(255, 138, 0, 0.1); } - - table.docutils th, - table.docutils td { - padding: 6px !important; + 100% { + box-shadow: 0 0 0 3px rgba(255, 138, 0, 0.2); } } -/* Style method signatures */ -.method-signature { - background-color: #f8f8f8; - border: 1px solid #e1e1e1; - border-radius: 3px; - padding: 10px; - font-family: 'Courier New', Courier, monospace; - margin: 10px 0; -} - -/* Style expectation method headers */ -.expectation-method h5 { - color: #2e8b57; - border-bottom: 2px solid #2e8b57; - padding-bottom: 5px; +/* Dark mode specific card shadow */ +html[data-theme="dark"] .expectation-card { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } -/* Style parameter lists */ -.parameter-list { - margin-left: 20px; +html[data-theme="dark"] .expectation-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); } -.parameter-list li { - margin-bottom: 5px; -} - -/* Add some spacing to sections */ -.section { - margin-bottom: 2em; -} - -/* Style code blocks */ -pre { - background-color: #f8f8f8; - border: 1px solid #e1e1e1; - border-radius: 4px; - padding: 10px; - overflow-x: auto; +/* Card header - scoped to expectation cards only */ +.expectation-card .card-header { + background: var(--pst-color-code-bg, var(--pst-color-surface)); + padding: 10px 12px; + border-bottom: 1px solid var(--pst-color-border); + min-height: 40px; + display: flex; + align-items: center; } -/* Style inline code */ -code { - background-color: #f1f1f1; - padding: 2px 4px; - border-radius: 3px; - font-family: 'Courier New', Courier, monospace; +.expectation-card .method-name { + font-family: var(--pst-font-family-monospace); + font-size: 0.95em; + font-weight: bold; + color: var(--pst-color-primary); + margin: 0; + word-wrap: break-word; + word-break: break-word; + overflow-wrap: break-word; + hyphens: auto; + line-height: 1.2; } -/* Style custom method documentation to match autodoc exactly */ -dl.py.method { - margin-bottom: 2em; +/* Card body - scoped to expectation cards only */ +.expectation-card .card-body { + padding: 10px 12px; + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; } -dt.sig.sig-object.py { - background-color: #f8f8f8; - border: 1px solid #d1d1d1; - border-radius: 3px; - padding: 10px; - font-family: 'SFMono-Regular', Menlo, 'Liberation Mono', Courier, monospace; +.expectation-card .card-description { + color: var(--pst-color-text-base); + margin-bottom: 8px; + line-height: 1.3; font-size: 0.9em; - margin-bottom: 0.5em; - font-weight: normal; -} - -dt.sig.sig-object.py .sig-name { - font-weight: bold; } -dt.sig.sig-object.py .sig-paren { - color: #666; +/* Tags - scoped to expectation cards only */ +.expectation-card .tags-container { + margin: 6px 0; } -dd.field-list { - margin-left: 2em; +.expectation-card .tag { + display: inline-block; + padding: 2px 6px; + border-radius: 8px; + font-size: 0.7em; + margin-right: 4px; + margin-bottom: 3px; } -dd.field-list dt { - font-weight: bold; - margin-bottom: 0.5em; +.expectation-card .category-tag { + background: var(--pst-color-primary-bg); + color: var(--pst-color-primary); + border: 1px solid var(--pst-color-primary); } -dd.field-list dd { - margin-left: 1em; - margin-bottom: 0.5em; +.expectation-card .subcategory-tag { + background: rgba(123, 31, 162, 0.1); + color: #7b1fa2; + border: 1px solid #ce93d8; } -/* Style the description content */ -dd.desc-content { - margin-left: 2em; +/* Dark mode adjustments for tags */ +html[data-theme="dark"] .expectation-card .subcategory-tag { + background: rgba(206, 147, 216, 0.2); + color: #ce93d8; } -dd.desc-content p { - margin-bottom: 1em; +/* Parameters preview - scoped to expectation cards only */ +.expectation-card .params-preview { + margin-top: 6px; + padding: 6px 8px; + background: var(--pst-color-code-bg, var(--pst-color-surface)); + border: 1px solid var(--pst-color-border); + border-radius: 3px; } -/* Style parameter lists */ -dl.field-list dt { +.expectation-card .params-title { font-weight: bold; - color: #2980b9; -} - -dl.field-list dd p strong { - color: #333; -} - -/* Ensure our custom expectations use standard Sphinx autodoc styling */ -dl.py.method > dt { - background-color: #f8f8f8 !important; - border: 1px solid #d1d1d1 !important; - border-radius: 3px !important; - padding: 10px !important; - font-family: 'SFMono-Regular', Menlo, 'Liberation Mono', Courier, monospace !important; - font-size: 0.9em !important; - margin-bottom: 0.5em !important; - font-weight: normal !important; -} - -dl.py.method > dd { - margin-left: 2em !important; -} - -/* Override any custom formatting that interferes with autodoc */ -.expectation-method h5, -.method-signature, -.parameter-list { - display: none !important; /* Hide any custom formatting */ -} - -/* Make sure field lists look standard */ -dl.py.method dd dl.field-list { - margin-top: 1em !important; + color: var(--pst-color-text-base); + margin: 0 0 3px 0; + font-size: 0.75em; } -dl.py.method dd dl.field-list dt { - font-weight: bold !important; - color: #2980b9 !important; - margin-bottom: 0.5em !important; -} - -dl.py.method dd dl.field-list dd { - margin-left: 1em !important; - margin-bottom: 1em !important; +.expectation-card .params-list { + font-family: var(--pst-font-family-monospace); + font-size: 0.75em; + color: var(--pst-color-text-muted); + margin: 0; } -/* Great Expectations Gallery Style Cards */ -.expectations-gallery { - margin: 2rem 0 !important; - padding: 0 !important; - width: 100% !important; +/* Card footer - scoped to expectation cards only */ +.expectation-card .card-footer { + padding: 8px 12px; + background: var(--pst-color-code-bg, var(--pst-color-surface)); + border-top: 1px solid var(--pst-color-border); + text-align: right; + margin-top: auto; } -/* Style category title headings for TOC inclusion */ -.category-title, -h1.category-title, -h2.category-title { - color: #2c3e50 !important; - border-bottom: 3px solid #3498db !important; - padding-bottom: 10px !important; - margin: 2em 0 1em 0 !important; - font-size: 1.4em !important; - font-weight: bold !important; +/* Buttons - scoped to expectation cards only */ +.expectation-card .btn { + display: inline-block; + padding: 5px 10px; + border-radius: 3px; + text-decoration: none; + font-size: 0.8em; + font-weight: 500; + transition: all 0.2s ease; } -.subcategory-title { - color: #34495e !important; - margin: 1.5em 0 1em 0 !important; - font-size: 1.2em !important; - font-weight: bold !important; +.expectation-card .btn-details { + background: var(--pst-color-primary); + color: var(--pst-color-on-primary) !important; + border: 1px solid var(--pst-color-primary); + text-decoration: none !important; } -.cards-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 32px !important; - margin: 2rem 0 !important; - align-items: start; - padding: 1rem 0 !important; - width: 100% !important; - max-width: none !important; +.expectation-card .btn-details:hover { + opacity: 0.85; + text-decoration: none !important; } -.expectation-card { - border: 1px solid #e1e8ed !important; - border-radius: 8px !important; - background: white !important; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important; - transition: all 0.3s ease !important; - overflow: hidden !important; - min-height: 320px !important; - display: flex !important; - flex-direction: column !important; - margin: 0 !important; - width: 100% !important; - min-width: 400px !important; -} - -/* Ensure expectation cards have proper internal padding */ -.expectation-card .card-header, -.expectation-card .card-body, -.expectation-card .card-footer { - box-sizing: border-box !important; +/* Responsive design */ +@media (max-width: 1400px) { + .expectations-gallery .cards-grid { + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 14px; + } } -.expectation-card .card-header { - padding: 20px 24px !important; -} +@media (max-width: 1200px) { + .expectations-gallery .cards-grid { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 12px; + } -.expectation-card .card-body { - padding: 24px !important; + .expectation-card { + min-width: 280px; + } } -.expectation-card .card-footer { - padding: 20px 24px !important; -} +@media (max-width: 768px) { + .expectations-gallery .cards-grid { + grid-template-columns: 1fr; + gap: 12px; + margin: 0.75rem 0; + padding: 0; + } -.expectation-card:hover { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - transform: translateY(-2px); + .expectation-card { + margin: 0; + min-height: 160px; + min-width: auto; + } } -.card-header { - background: #f8f9fa !important; - padding: 20px 24px !important; - border-bottom: 1px solid #e1e8ed !important; - min-height: 60px !important; - display: flex !important; - align-items: center !important; +/* Allow class signatures to wrap properly */ +dt.sig.sig-object.py { + word-wrap: break-word; + word-break: break-word; + overflow-wrap: break-word; + white-space: normal; } -.method-name { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-size: 1.1em; - font-weight: bold; - color: #2c3e50; - margin: 0; +/* Specifically target the class name within signatures */ +dt.sig.sig-object.py .sig-prename, +dt.sig.sig-object.py .sig-name { word-wrap: break-word; word-break: break-word; overflow-wrap: break-word; - hyphens: auto; - line-height: 1.3; } -.card-body { - padding: 24px !important; - flex: 1 !important; - display: flex !important; - flex-direction: column !important; - justify-content: space-between !important; +/* Summary Table Styling */ +.summary-table { + width: 100%; + margin: 1.5rem 0; + border-collapse: collapse; } -.card-description { - color: #555; - margin-bottom: 15px; - line-height: 1.5; +.summary-table-header { + background: var(--pst-color-primary); + color: white; + font-weight: 600; + text-align: left; } -.tags-container { - margin: 15px 0; +.summary-table-header p { + margin: 0; + padding: 10px 12px; } -.tag { - display: inline-block; - padding: 4px 8px; - border-radius: 12px; - font-size: 0.8em; - margin-right: 8px; - margin-bottom: 5px; +.summary-table-row { + border-bottom: 1px solid var(--pst-color-border); } -.category-tag { - background: #e3f2fd; - color: #1976d2; - border: 1px solid #bbdefb; +.summary-category-cell { + background: var(--pst-color-code-bg, var(--pst-color-surface)); + border-right: 2px solid var(--pst-color-primary); + vertical-align: top; + font-weight: 600; + color: var(--pst-color-primary); } -.subcategory-tag { - background: #f3e5f5; - color: #7b1fa2; - border: 1px solid #ce93d8; +.summary-category-cell p { + margin: 0; + padding: 12px; + font-size: 0.95em; } -.params-preview { - margin-top: 15px; - padding: 10px; - background: #f8f9fa; - border-radius: 4px; +.summary-subcategory-cell { + background: var(--pst-color-surface); + vertical-align: top; + font-weight: 500; + color: var(--pst-color-text-base); } -.params-title { - font-weight: bold; - color: #495057; - margin: 0 0 5px 0; +.summary-subcategory-cell p { + margin: 0; + padding: 10px 12px; font-size: 0.9em; } -.params-list { - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-size: 0.85em; - color: #6c757d; - margin: 0; +.summary-expectations-cell { + background: var(--pst-color-surface); + padding: 8px 12px; + vertical-align: top; } -.card-footer { - padding: 20px 24px !important; - background: #f8f9fa !important; - border-top: 1px solid #e1e8ed !important; - text-align: right !important; - margin-top: auto !important; +/* Expectation badges container */ +.expectation-badges { + display: flex; + flex-wrap: wrap; + gap: 6px; + align-items: flex-start; } -.btn { +/* Individual expectation badge */ +.expectation-badge { display: inline-block; - padding: 8px 16px; + padding: 4px 10px; + background: var(--pst-color-code-bg, var(--pst-color-surface)); + border: 1px solid var(--pst-color-border); border-radius: 4px; + font-family: var(--pst-font-family-monospace); + font-size: 0.8em; + color: var(--pst-color-primary); text-decoration: none; - font-size: 0.9em; - font-weight: 500; transition: all 0.2s ease; + white-space: nowrap; } -.btn-details { - background: #007bff; - color: white !important; - border: 1px solid #007bff; - text-decoration: none !important; -} - -.btn-details:hover { - background: #0056b3; - border-color: #0056b3; - text-decoration: none !important; - color: white !important; +.expectation-badge:hover { + background: var(--pst-color-primary); + color: white; + border-color: var(--pst-color-primary); + text-decoration: none; + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); } -/* Responsive design */ -@media (max-width: 1400px) { - .cards-grid { - grid-template-columns: repeat(auto-fit, minmax(380px, 1fr)) !important; - gap: 28px !important; - } +html[data-theme="dark"] .expectation-badge:hover { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); } +/* Responsive adjustments for summary table */ @media (max-width: 1200px) { - .cards-grid { - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)) !important; - gap: 24px !important; - } - - .expectation-card { - min-width: 350px !important; + .expectation-badge { + font-size: 0.75em; + padding: 3px 8px; } } @media (max-width: 768px) { - .cards-grid { - grid-template-columns: 1fr !important; - gap: 24px !important; - margin: 1.5rem 0 !important; - padding: 0 !important; + .summary-table { + font-size: 0.9em; } - .expectation-card { - margin: 0 !important; - min-height: 280px !important; - min-width: auto !important; + .summary-category-cell p, + .summary-subcategory-cell p { + padding: 8px; + font-size: 0.85em; + } + + .summary-expectations-cell { + padding: 6px 8px; } - .bd-content { - padding-left: 1.5rem !important; - padding-right: 1.5rem !important; + .expectation-badges { + gap: 4px; } - .bd-article-container { - padding: 0 1.5rem !important; + .expectation-badge { + font-size: 0.7em; + padding: 3px 6px; } } diff --git a/docs/source/conf.py b/docs/source/conf.py index 2a90d41..958d231 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -31,20 +31,20 @@ ] # Theme -html_theme = 'pydata_sphinx_theme' +html_theme = 'sphinx_book_theme' -# PyData theme options for modern, full-width usage +# Sphinx Book Theme options html_theme_options = { - "use_edit_page_button": False, - "navigation_depth": 3, - "show_prev_next": True, - "navbar_persistent": ["search-button"], - "navbar_center": ["navbar-nav"], - "navbar_end": [], - "sidebar_includehidden": True, - "primary_sidebar_end": ["page-toc"], - "secondary_sidebar_items": [], - "show_toc_level": 3, + "repository_url": "https://github.com/getyourguide/dataframe-expectations", + "use_repository_button": True, + "use_edit_button": False, + "use_issues_button": True, + "use_download_button": True, + "path_to_docs": "docs/source", + "show_navbar_depth": 2, + "show_toc_level": 2, + "home_page_in_toc": True, + "navigation_with_keys": True, } # Autodoc settings @@ -75,7 +75,7 @@ ] # Configure HTML title and layout -html_title = f"{project} v{release} Documentation" +html_title = f"{project} v{release}" html_short_title = project # PyData theme context diff --git a/pyproject.toml b/pyproject.toml index d97f45c..0555f61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dev = [ ] docs = [ "sphinx>=4.0.0", - "pydata-sphinx-theme>=0.13.0", + "sphinx-book-theme>=1.0.0", "sphinx-autobuild>=2021.3.14", "pyspark>=3.3.0", "pandas>=1.5.0", diff --git a/scripts/generate_suite_stubs.py b/scripts/generate_suite_stubs.py index 9058ffb..ae0a37a 100755 --- a/scripts/generate_suite_stubs.py +++ b/scripts/generate_suite_stubs.py @@ -280,7 +280,7 @@ def update_pyi_file(dry_run: bool = False) -> bool: return False if dry_run: - print("❌ Stub file is out of date. Run without --check to update.") + print("❌ Stub file is out of date. Run uv run python scripts/generate_suite_stubs.py to update.") return True # Write the .pyi file diff --git a/uv.lock b/uv.lock index 2a753d6..b5a1077 100644 --- a/uv.lock +++ b/uv.lock @@ -308,7 +308,7 @@ toml = [ [[package]] name = "dataframe-expectations" -version = "0.4.0" +version = "0.5.0" source = { virtual = "." } dependencies = [ { name = "pandas" }, @@ -328,10 +328,10 @@ dev = [ ] docs = [ { name = "pandas" }, - { name = "pydata-sphinx-theme" }, { name = "pyspark" }, { name = "sphinx" }, { name = "sphinx-autobuild" }, + { name = "sphinx-book-theme" }, { name = "tabulate" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] @@ -355,10 +355,10 @@ dev = [ ] docs = [ { name = "pandas", specifier = ">=1.5.0" }, - { name = "pydata-sphinx-theme", specifier = ">=0.13.0" }, { name = "pyspark", specifier = ">=3.3.0" }, { name = "sphinx", specifier = ">=4.0.0" }, { name = "sphinx-autobuild", specifier = ">=2021.3.14" }, + { name = "sphinx-book-theme", specifier = ">=1.0.0" }, { name = "tabulate", specifier = ">=0.8.9" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=2.0.0" }, ] @@ -872,20 +872,21 @@ wheels = [ [[package]] name = "pydata-sphinx-theme" -version = "0.16.1" +version = "0.15.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "accessible-pygments" }, { name = "babel" }, { name = "beautifulsoup4" }, { name = "docutils" }, + { name = "packaging" }, { name = "pygments" }, { name = "sphinx" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/20/bb50f9de3a6de69e6abd6b087b52fa2418a0418b19597601605f855ad044/pydata_sphinx_theme-0.16.1.tar.gz", hash = "sha256:a08b7f0b7f70387219dc659bff0893a7554d5eb39b59d3b8ef37b8401b7642d7", size = 2412693, upload-time = "2024-12-17T10:53:39.537Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/ea/3ab478cccacc2e8ef69892c42c44ae547bae089f356c4b47caf61730958d/pydata_sphinx_theme-0.15.4.tar.gz", hash = "sha256:7762ec0ac59df3acecf49fd2f889e1b4565dbce8b88b2e29ee06fdd90645a06d", size = 2400673, upload-time = "2024-06-25T19:28:45.041Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/0d/8ba33fa83a7dcde13eb3c1c2a0c1cc29950a048bfed6d9b0d8b6bd710b4c/pydata_sphinx_theme-0.16.1-py3-none-any.whl", hash = "sha256:225331e8ac4b32682c18fcac5a57a6f717c4e632cea5dd0e247b55155faeccde", size = 6723264, upload-time = "2024-12-17T10:53:35.645Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d3/c622950d87a2ffd1654208733b5bd1c5645930014abed8f4c0d74863988b/pydata_sphinx_theme-0.15.4-py3-none-any.whl", hash = "sha256:2136ad0e9500d0949f96167e63f3e298620040aea8f9c74621959eda5d4cf8e6", size = 4640157, upload-time = "2024-06-25T19:28:42.383Z" }, ] [[package]] @@ -1158,6 +1159,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" }, ] +[[package]] +name = "sphinx-book-theme" +version = "1.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydata-sphinx-theme" }, + { name = "sphinx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/19/d002ed96bdc7738c15847c730e1e88282d738263deac705d5713b4d8fa94/sphinx_book_theme-1.1.4.tar.gz", hash = "sha256:73efe28af871d0a89bd05856d300e61edce0d5b2fbb7984e84454be0fedfe9ed", size = 439188, upload-time = "2025-02-20T16:32:32.581Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/9e/c41d68be04eef5b6202b468e0f90faf0c469f3a03353f2a218fd78279710/sphinx_book_theme-1.1.4-py3-none-any.whl", hash = "sha256:843b3f5c8684640f4a2d01abd298beb66452d1b2394cd9ef5be5ebd5640ea0e1", size = 433952, upload-time = "2025-02-20T16:32:31.009Z" }, +] + [[package]] name = "sphinxcontrib-applehelp" version = "2.0.0"