diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml
index ef168e4..ee7021d 100644
--- a/.github/workflows/push.yml
+++ b/.github/workflows/push.yml
@@ -6,7 +6,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
poetry-version: [2.1]
os: [ubuntu-24.04, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
@@ -26,7 +26,7 @@ jobs:
- name: run bench (pure python)
run: poetry run python -m bench.run
- name: mypyc
- run: poetry run mypyc simple_html/utils.py
+ run: poetry run mypyc simple_html/core.py
- name: run tests
run: poetry run pytest
- name: run bench (compiled)
diff --git a/README.md b/README.md
index e0983d6..aafda70 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
## Why use it?
- clean syntax
- fully-typed
-- speed -- faster even than jinja
+- speed -- often faster than jinja
- zero dependencies
- escaped by default
- usually renders fewer bytes than templating
@@ -18,6 +18,7 @@
```python
from simple_html import h1, render
+
node = h1("Hello World!")
render(node)
@@ -36,6 +37,7 @@ Here's a fuller-featured example:
```python
from simple_html import render, DOCTYPE_HTML5, html, head, title, body, h1, div, p, br, ul, li
+
render(
DOCTYPE_HTML5,
html(
@@ -79,6 +81,7 @@ As you might have noticed, there are several ways to use `Tag`s:
```python
from simple_html import br, div, h1, img, span, render
+
# raw node renders to empty tag
render(br)
#
@@ -106,6 +109,7 @@ escaped by default; `SafeString`s can be used to bypass escaping.
```python
from simple_html import br, p, SafeString, render
+
node = p("Escaped & stuff",
br,
SafeString("Not escaped & stuff"))
@@ -121,6 +125,7 @@ that Tag attributes with `None` as the value will only render the attribute name
```python
from simple_html import div, render
+
node = div({"empty-str-attribute": "",
"key-only-attr": None})
@@ -133,6 +138,7 @@ String attributes are escaped by default -- both keys and values. You can use `S
```python
from simple_html import div, render, SafeString
+
render(
div({"":""})
)
@@ -149,6 +155,7 @@ You can also use `int`, `float`, and `Decimal` instances for attribute values.
from decimal import Decimal
from simple_html import div, render, SafeString
+
render(
div({"x": 1, "y": 2.3, "z": Decimal('3.45')})
)
@@ -161,6 +168,7 @@ You can render inline CSS styles with `render_styles`:
```python
from simple_html import div, render, render_styles
+
styles = render_styles({"min-width": "25px"})
node = div({"style": styles}, "cool")
@@ -184,6 +192,7 @@ You can pass many items as a `Tag`'s children using `*args`, lists or generators
from typing import Generator
from simple_html import div, render, Node, br, p
+
div(
*["neat", br], p("cool")
)
@@ -214,6 +223,7 @@ For convenience, most common tags are provided, but you can also create your own
```python
from simple_html import Tag, render
+
custom_elem = Tag("custom-elem")
# works the same as any other tag
@@ -225,3 +235,89 @@ node = custom_elem(
render(node)
# Wow
```
+
+### Optimization
+
+#### `prerender`
+
+`prerender` is a very simple function. It just `render`s a `Node` and puts the resulting string inside
+a `SafeString` (so its contents won't be escaped again). It's most useful for prerendering at the module level,
+which ensures the render operation happens only once. A simple use case might be a website's footer:
+
+```python
+from simple_html import SafeString, prerender, footer, div, a, head, body, title, h1, html, render
+
+
+prerendered_footer: SafeString = prerender(
+ footer(
+ div(a({"href": "/about"}, "About Us")),
+ div(a({"href": "/blog"}, "Blog")),
+ div(a({"href": "/contact"}, "Contact"))
+ )
+)
+
+
+def render_page(page_title: str) -> str:
+ return render(
+ html(
+ head(title(page_title)),
+ body(
+ h1(page_title),
+ prerendered_footer # this is extremely fast to render
+ )
+ )
+ )
+```
+This greatly reduces the amount of work `render` needs to do on the prerendered content when outputting HTML.
+
+#### Caching
+You may want to cache rendered content. This is easy to do; the main thing to keep in
+mind is you'll likely want to return a `SafeString`. For example, here's how you might cache with `lru_cache`:
+
+```python
+from simple_html import prerender, SafeString, h1
+from functools import lru_cache
+
+
+@lru_cache
+def greeting(name: str) -> SafeString:
+ return prerender(
+ h1(f"Hello, {name}")
+ )
+```
+
+One thing to remember is that not all variants of `Node` are hashable, and thus cannot be passed directly to a function
+where the arguments constitute the cache key -- e.g. lists and generators are not hashable, but they can be
+valid `Node`s. Another way to use `prerender` in combination with a caching function is to prerender arguments:
+
+```python
+from simple_html import prerender, SafeString, h1, div, html, body, head, ul, li
+from functools import lru_cache
+
+
+@lru_cache
+def cached_content(children: SafeString) -> SafeString:
+ return prerender(
+ div(
+ h1("This content is cached according to the content of the children"),
+ children,
+ # presumably this function would have a lot more elements for it to be worth
+ # the caching overhead
+ )
+ )
+
+def page(words_to_render: list[str]):
+ return html(
+ head,
+ body(
+ cached_content(
+ prerender(ul([
+ li(word) for word in words_to_render
+ ]))
+ )
+ )
+ )
+```
+Keep in mind that using `prerender` on dynamic content -- not at the module level -- still incurs all the overhead
+of `render` each time that content is rendered, so, for this approach to make sense, the prerendered content should
+be a small portion of the full content of the `cached_content` function.
\ No newline at end of file
diff --git a/bench/simple.py b/bench/simple.py
index 6b92e29..cda1ca8 100644
--- a/bench/simple.py
+++ b/bench/simple.py
@@ -22,6 +22,7 @@
nav, a, main, section, article, aside, footer, span, img, time,
blockquote, code, pre, form, label, input_, textarea, button, table, thead, tbody, tr, th, td
)
+from simple_html.core import Node, prerender
def hello_world_empty(objs: List[None]) -> None:
@@ -29,22 +30,31 @@ def hello_world_empty(objs: List[None]) -> None:
render(h1("Hello, World!"))
+def _basic_html(list_items: list[Node]) -> Node:
+ return html(
+ head(title("A Great Web page!")),
+ body(
+ h1({"class": "great header",
+ "id": "header1",
+ "other_attr": "5"},
+ "Welcome!"),
+ div(
+ p("What a great web page!!!", br, br),
+ ul(list_items)
+ )
+ )
+ )
+
+
def basic(objs: List[Tuple[str, str, List[str]]]) -> None:
for title_, content, oks in objs:
- render(
- DOCTYPE_HTML5,
- html(
- head(title("A Great Web page!")),
- body(
- h1({"class": "great header",
- "id": "header1",
- "other_attr": "5"},
- "Welcome!"),
- div(
- p("What a great web page!!!", br, br),
- ul([
- li({"class": "item-stuff"}, SafeString(ss))
- for ss in oks])))))
+ render(DOCTYPE_HTML5,
+ _basic_html(
+ [
+ li({"class": "item-stuff"}, SafeString(ss))
+ for ss in oks
+ ]
+ ))
def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
@@ -95,45 +105,62 @@ def basic_long(objs: List[Tuple[str, str, List[str]]]) -> None:
)
+def _lorem_html(title_: str) -> Node:
+ return html(
+ {"lang": "en"},
+ head(
+ meta({"charset": "UTF-8"}),
+ meta(
+ {
+ "name": "viewport",
+ "content": "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0",
+ }
+ ),
+ meta({"http-equiv": "X-UA-Compatible", "content": "ie=edge"}),
+ title(title_),
+ ),
+ body(
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+ ),
+ )
+
+
def lorem_ipsum(titles: List[str]) -> None:
for t in titles:
- render(
- html(
- {"lang": "en"},
- head(
- meta({"charset": "UTF-8"}),
- meta(
- {
- "name": "viewport",
- "content": "width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0",
- }
- ),
- meta({"http-equiv": "X-UA-Compatible", "content": "ie=edge"}),
- title(t),
- ),
- body(
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
- ),
- )
- )
+ render(_lorem_html(t))
-def large_page(titles: list[str]) -> None:
- for t in titles:
- render(
- DOCTYPE_HTML5,
- html({"lang": "en"},
- head(
- meta({"charset": "UTF-8"}),
- meta({"name": "viewport", "content": "width=device-width, initial-scale=1.0"}),
- meta({"name": "description",
- "content": "Tech Insights - Your source for the latest in web development, AI, and programming trends"}),
- meta({"name": "keywords",
- "content": "web development, programming, AI, JavaScript, Python, tech news"}),
- title(t),
- link({"rel": "stylesheet", "href": "/static/css/main.css"}),
- link({"rel": "icon", "type": "image/x-icon", "href": "/favicon.ico"}),
- style("""
+def _article(heading: str,
+ published_date_iso: str,
+ published_data_readable,
+ author: str,
+ read_time_minutes: str,
+ content: list[Node],
+ is_featured: bool = False):
+ return article(
+ {"class": "post" + ("featured-post" if is_featured else ""), },
+ h2(heading),
+ p({"class": "post-meta"},
+ "Published on ", time({"datetime": published_date_iso}, published_data_readable),
+ " by ", span({"class": "author"}, author),
+ " | ", span({"class": "read-time"}, read_time_minutes, " min read")
+ ),
+ content
+ )
+
+
+def _get_head(title_: str) -> Node:
+ return head(
+ meta({"charset": "UTF-8"}),
+ meta({"name": "viewport", "content": "width=device-width, initial-scale=1.0"}),
+ meta({"name": "description",
+ "content": "Tech Insights - Your source for the latest in web development, AI, and programming trends"}),
+ meta({"name": "keywords",
+ "content": "web development, programming, AI, JavaScript, Python, tech news"}),
+ title(title_),
+ link({"rel": "stylesheet", "href": "/static/css/main.css"}),
+ link({"rel": "icon", "type": "image/x-icon", "href": "/favicon.ico"}),
+ style("""
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
.site-header { background: #2c3e50; color: white; padding: 1rem 0; }
@@ -150,318 +177,341 @@ def large_page(titles: list[str]) -> None:
.stats-table th, .stats-table td { padding: 0.5rem; border: 1px solid #ddd; text-align: left; }
.stats-table th { background: #f0f0f0; }
""")
- ),
- body(
- header({"class": "site-header"},
- div({"class": "container"},
- h1({"class": "site-title"}, "Tech Insights"),
- nav(
- ul({"class": "nav-menu"},
- li(a({"href": "/"}, "Home")),
- li(a({"href": "/tutorials"}, "Tutorials")),
- li(a({"href": "/articles"}, "Articles")),
- li(a({"href": "/reviews"}, "Reviews")),
- li(a({"href": "/resources"}, "Resources")),
- li(a({"href": "/about"}, "About")),
- li(a({"href": "/contact"}, "Contact"))
+ )
+
+_footer = prerender(
+ footer({"class": "site-footer"},
+ div({"class": "container"},
+ div({"class": "footer-content"},
+ div({"class": "footer-section"},
+ h4("About Tech Insights"),
+ p("Your go-to resource for web development tutorials, programming guides, and the latest technology trends. We help developers stay current with industry best practices.")
+ ),
+ div({"class": "footer-section"},
+ h4("Quick Links"),
+ ul(
+ li(a({"href": "/privacy"}, "Privacy Policy")),
+ li(a({"href": "/terms"}, "Terms of Service")),
+ li(a({"href": "/sitemap"}, "Sitemap")),
+ li(a({"href": "/advertise"}, "Advertise"))
)
- )
- )
- ),
- main({"class": "container main-content"},
- section({"class": "content-area"},
- article({"class": "post featured-post"},
- h2("Complete Guide to Modern Web Development in 2024"),
- p({"class": "post-meta"},
- "Published on ", time({"datetime": "2024-03-15"}, "March 15, 2024"),
- " by ", span({"class": "author"}, "Sarah Johnson"),
- " | ", span({"class": "read-time"}, "12 min read")
- ),
- img({"src": "/images/web-dev-2024.jpg",
- "alt": "Modern web development tools and frameworks",
- "style": "width: 100%; height: 300px; object-fit: cover; border-radius: 8px;"}),
- p(
- "Web development has evolved significantly in recent years, transforming from simple static pages ",
- "to complex, interactive applications that power our digital world. The landscape continues to change ",
- "rapidly, driven by new technologies, frameworks, and methodologies that promise to make development ",
- "faster, more efficient, and more accessible."
- ),
- h3("Key Technologies Shaping the Future"),
- p("The modern web development ecosystem is built around several core technologies:"),
- ul(
- li("**Component-based frameworks** like React, Vue, and Angular that promote reusable UI components"),
- li("**Progressive Web Apps (PWAs)** that bridge the gap between web and native applications"),
- li("**Serverless architectures** using AWS Lambda, Vercel Functions, and Netlify Functions"),
- li("**JAMstack** (JavaScript, APIs, Markup) for better performance and security"),
- li("**GraphQL** for more efficient data fetching and API design"),
- li("**TypeScript** for type-safe JavaScript development"),
- li("**Edge computing** for reduced latency and improved user experience")
- ),
- h3("Framework Comparison"),
- table({"class": "stats-table"},
- thead(
- tr(
- th("Framework"),
- th("Learning Curve"),
- th("Performance"),
- th("Community"),
- th("Use Case")
- )
- ),
- tbody(
- tr(
- td("React"),
- td("Medium"),
- td("High"),
- td("Very Large"),
- td("Complex UIs, SPAs")
- ),
- tr(
- td("Vue.js"),
- td("Easy"),
- td("High"),
- td("Large"),
- td("Rapid prototyping, SME apps")
- ),
- tr(
- td("Angular"),
- td("Steep"),
- td("High"),
- td("Large"),
- td("Enterprise applications")
- ),
- tr(
- td("Svelte"),
- td("Easy"),
- td("Very High"),
- td("Growing"),
- td("Performance-critical apps")
- )
- )
- ),
- h3("Code Example: Modern Component"),
- p("Here's an example of a modern React component using hooks and TypeScript:"),
- pre({"class": "code-block"},
- code("""
- interface User {
- id: number;
- name: string;
- email: string;
- }
-
- const UserProfile: React.FC<{ userId: number }> = ({ userId }) => {
- const [user, setUser] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- fetchUser(userId)
- .then(setUser)
- .finally(() => setLoading(false));
- }, [userId]);
-
- if (loading) return Loading...
;
- if (!user) return User not found
;
-
- return (
-
-
{user.name}
-
{user.email}
-
- );
- };
- """)
+ ),
+ div({"class": "footer-section"},
+ h4("Contact Info"),
+ p("Email: hello@techinsights.dev"),
+ p("Location: San Francisco, CA"),
+ p("Phone: (555) 123-4567")
+ )
+ ),
+ hr,
+ div({"class": "footer-bottom"},
+ p("© 2024 Tech Insights. All rights reserved. Built with simple_html library."),
+ p("Made with ❤️ for the developer community")
+ )
+ )
+ )
+)
+
+def _html(t: str,
+ articles: list[Node]) -> Node:
+ return html({"lang": "en"},
+ _get_head(title_=t),
+ body(
+ header({"class": "site-header"},
+ div({"class": "container"},
+ h1({"class": "site-title"}, "Tech Insights"),
+ nav(
+ ul({"class": "nav-menu"},
+ li(a({"href": "/"}, "Home")),
+ li(a({"href": "/tutorials"}, "Tutorials")),
+ li(a({"href": "/articles"}, "Articles")),
+ li(a({"href": "/reviews"}, "Reviews")),
+ li(a({"href": "/resources"}, "Resources")),
+ li(a({"href": "/about"}, "About")),
+ li(a({"href": "/contact"}, "Contact"))
+ )
+ )
+ )
+ ),
+ main({"class": "container main-content"},
+ section({"class": "content-area"},
+ articles,
+ section({"class": "comment-section"},
+ h3("Join the Discussion"),
+ form({"class": "comment-form", "action": "/submit-comment", "method": "POST"},
+ div(
+ label({"for": "name"}, "Name:"),
+ br,
+ input_(
+ {"type": "text", "id": "name", "name": "name", "required": None})
+ ),
+ div(
+ label({"for": "email"}, "Email:"),
+ br,
+ input_(
+ {"type": "email", "id": "email", "name": "email",
+ "required": None})
+ ),
+ div(
+ label({"for": "comment"}, "Your Comment:"),
+ br,
+ textarea(
+ {"id": "comment", "name": "comment", "rows": "5", "cols": "50",
+ "required": None})
),
- h3("Best Practices for 2024"),
- p("As we move forward in 2024, several best practices have emerged:"),
- ol(
- li("**Performance First**: Optimize for Core Web Vitals and user experience metrics"),
- li("**Accessibility by Default**: Implement WCAG guidelines from the start of development"),
- li("**Security-First Mindset**: Use CSP headers, sanitize inputs, and follow OWASP guidelines"),
- li("**Mobile-First Design**: Start with mobile layouts and progressively enhance for larger screens"),
- li("**Sustainable Web Development**: Optimize for energy efficiency and reduced carbon footprint")
- ),
- blockquote(
- p("\"The best web developers are those who understand that technology should serve users, not the other way around.\""),
- footer("— John Doe, Senior Frontend Architect at TechCorp")
- )
- ),
-
- article({"class": "post"},
- h2("The Rise of AI in Development: Tools and Techniques"),
- p({"class": "post-meta"},
- "Published on ", time({"datetime": "2024-03-10"}, "March 10, 2024"),
- " by ", span({"class": "author"}, "Michael Chen"),
- " | ", span({"class": "read-time"}, "8 min read")
- ),
- p(
- "Artificial Intelligence is fundamentally transforming how we write, test, and deploy code. ",
- "From intelligent autocomplete suggestions to automated bug detection and code generation, ",
- "AI tools are becoming essential companions for modern developers."
- ),
- h3("Popular AI Development Tools"),
- ul(
- li("**GitHub Copilot**: AI-powered code completion and generation"),
- li("**ChatGPT & GPT-4**: Code explanation, debugging, and architecture advice"),
- li("**Amazon CodeWhisperer**: Real-time code suggestions with security scanning"),
- li("**DeepCode**: AI-powered code review and vulnerability detection"),
- li("**Kite**: Intelligent code completion for Python and JavaScript")
- ),
- p(
- "These tools don't replace developers but rather augment their capabilities, ",
- "allowing them to focus on higher-level problem solving and creative solutions."
- )
- ),
-
- article({"class": "post"},
- h2("Python vs JavaScript: Which Language to Learn in 2024?"),
- p({"class": "post-meta"},
- "Published on ", time({"datetime": "2024-03-05"}, "March 5, 2024"),
- " by ", span({"class": "author"}, "Emily Rodriguez"),
- " | ", span({"class": "read-time"}, "10 min read")
- ),
- p(
- "The eternal debate continues: should new developers learn Python or JavaScript first? ",
- "Both languages have their strengths and use cases, and the answer largely depends on ",
- "your career goals and the type of projects you want to work on."
- ),
- h3("Python Advantages"),
- ul(
- li("Simple, readable syntax that's beginner-friendly"),
- li("Excellent for data science, machine learning, and AI"),
- li("Strong in automation, scripting, and backend development"),
- li("Huge ecosystem of libraries and frameworks (Django, Flask, NumPy, pandas)")
- ),
- h3("JavaScript Advantages"),
- ul(
- li("Essential for web development (frontend and backend with Node.js)"),
- li("Immediate visual feedback when learning"),
- li("Huge job market and demand"),
- li("Versatile: runs in browsers, servers, mobile apps, and desktop applications")
- ),
- p("The truth is, both languages are valuable, and learning one makes learning the other easier.")
- ),
-
- section({"class": "comment-section"},
- h3("Join the Discussion"),
- form({"class": "comment-form", "action": "/submit-comment", "method": "POST"},
- div(
- label({"for": "name"}, "Name:"),
- br,
- input_({"type": "text", "id": "name", "name": "name", "required": None})
- ),
- div(
- label({"for": "email"}, "Email:"),
- br,
- input_(
- {"type": "email", "id": "email", "name": "email", "required": None})
- ),
- div(
- label({"for": "comment"}, "Your Comment:"),
- br,
- textarea({"id": "comment", "name": "comment", "rows": "5", "cols": "50",
- "required": None})
- ),
- br,
- button({"type": "submit"}, "Post Comment")
- )
- )
- ),
-
- aside({"class": "sidebar"},
- section(
- h3("Popular Tags"),
- div({"class": "tag-cloud"},
- a({"href": "/tags/python", "class": "tag"}, "Python"),
- a({"href": "/tags/javascript", "class": "tag"}, "JavaScript"),
- a({"href": "/tags/react", "class": "tag"}, "React"),
- a({"href": "/tags/ai", "class": "tag"}, "AI & ML"),
- a({"href": "/tags/webdev", "class": "tag"}, "Web Dev"),
- a({"href": "/tags/nodejs", "class": "tag"}, "Node.js"),
- a({"href": "/tags/typescript", "class": "tag"}, "TypeScript"),
- a({"href": "/tags/vue", "class": "tag"}, "Vue.js")
- )
- ),
-
- section(
- h3("Latest Tutorials"),
- ul(
- li(a({"href": "/tutorial/rest-api-python"},
- "Building REST APIs with Python and FastAPI")),
- li(a({"href": "/tutorial/react-hooks"}, "Advanced React Hooks Patterns")),
- li(a({"href": "/tutorial/docker-basics"}, "Docker for Beginners: Complete Guide")),
- li(a({"href": "/tutorial/graphql-intro"}, "Introduction to GraphQL")),
- li(a({"href": "/tutorial/css-grid"}, "Mastering CSS Grid Layout"))
- )
- ),
-
- section(
- h3("Recommended Books"),
- ul(
- li("Clean Code by Robert C. Martin"),
- li("You Don't Know JS by Kyle Simpson"),
- li("Python Crash Course by Eric Matthes"),
- li("Designing Data-Intensive Applications by Martin Kleppmann"),
- li("The Pragmatic Programmer by Andy Hunt")
- )
- ),
-
- section(
- h3("Follow Us"),
- div(
- p("Stay updated with the latest tech trends:"),
- ul(
- li(a({"href": "https://twitter.com/techinsights"}, "Twitter")),
- li(a({"href": "https://linkedin.com/company/techinsights"}, "LinkedIn")),
- li(a({"href": "/newsletter"}, "Newsletter")),
- li(a({"href": "/rss"}, "RSS Feed"))
- )
- )
- ),
-
- section(
- h3("Site Statistics"),
- table({"class": "stats-table"},
- tbody(
- tr(td("Total Articles"), td("247")),
- tr(td("Active Users"), td("12,394")),
- tr(td("Comments"), td("3,891")),
- tr(td("Code Examples"), td("1,205"))
- )
- )
- )
- )
+ br,
+ button({"type": "submit"}, "Post Comment")
+ )
+ )
+ ),
+
+ aside({"class": "sidebar"},
+ section(
+ h3("Popular Tags"),
+ div({"class": "tag-cloud"},
+ a({"href": "/tags/python", "class": "tag"}, "Python"),
+ a({"href": "/tags/javascript", "class": "tag"}, "JavaScript"),
+ a({"href": "/tags/react", "class": "tag"}, "React"),
+ a({"href": "/tags/ai", "class": "tag"}, "AI & ML"),
+ a({"href": "/tags/webdev", "class": "tag"}, "Web Dev"),
+ a({"href": "/tags/nodejs", "class": "tag"}, "Node.js"),
+ a({"href": "/tags/typescript", "class": "tag"}, "TypeScript"),
+ a({"href": "/tags/vue", "class": "tag"}, "Vue.js")
+ )
+ ),
+
+ section(
+ h3("Latest Tutorials"),
+ ul(
+ li(a({"href": "/tutorial/rest-api-python"},
+ "Building REST APIs with Python and FastAPI")),
+ li(a({"href": "/tutorial/react-hooks"}, "Advanced React Hooks Patterns")),
+ li(a({"href": "/tutorial/docker-basics"},
+ "Docker for Beginners: Complete Guide")),
+ li(a({"href": "/tutorial/graphql-intro"}, "Introduction to GraphQL")),
+ li(a({"href": "/tutorial/css-grid"}, "Mastering CSS Grid Layout"))
+ )
+ ),
+
+ section(
+ h3("Recommended Books"),
+ ul(
+ li("Clean Code by Robert C. Martin"),
+ li("You Don't Know JS by Kyle Simpson"),
+ li("Python Crash Course by Eric Matthes"),
+ li("Designing Data-Intensive Applications by Martin Kleppmann"),
+ li("The Pragmatic Programmer by Andy Hunt")
+ )
+ ),
+
+ section(
+ h3("Follow Us"),
+ div(
+ p("Stay updated with the latest tech trends:"),
+ ul(
+ li(a({"href": "https://twitter.com/techinsights"}, "Twitter")),
+ li(a({"href": "https://linkedin.com/company/techinsights"}, "LinkedIn")),
+ li(a({"href": "/newsletter"}, "Newsletter")),
+ li(a({"href": "/rss"}, "RSS Feed"))
+ )
+ )
+ ),
+
+ section(
+ h3("Site Statistics"),
+ table({"class": "stats-table"},
+ tbody(
+ tr(td("Total Articles"), td("247")),
+ tr(td("Active Users"), td("12,394")),
+ tr(td("Comments"), td("3,891")),
+ tr(td("Code Examples"), td("1,205"))
+ )
+ )
+ )
+ )
+ ),
+
+ _footer
+ )
+ )
+
+
+def large_page(titles: list[str]) -> None:
+ for t in titles:
+ articles = [
+ _article(
+ "Complete Guide to Modern Web Development in 2024",
+ "2024-03-15",
+ "March 15, 2024",
+ "Sarah Johnson",
+ read_time_minutes=12,
+ content=[
+ img({"src": "/images/web-dev-2024.jpg",
+ "alt": "Modern web development tools and frameworks",
+ "style": "width: 100%; height: 300px; object-fit: cover; border-radius: 8px;"}),
+ p(
+ "Web development has evolved significantly in recent years, transforming from simple static pages ",
+ "to complex, interactive applications that power our digital world. The landscape continues to change ",
+ "rapidly, driven by new technologies, frameworks, and methodologies that promise to make development ",
+ "faster, more efficient, and more accessible."
+ ),
+ h3("Key Technologies Shaping the Future"),
+ p("The modern web development ecosystem is built around several core technologies:"),
+ ul(
+ li("**Component-based frameworks** like React, Vue, and Angular that promote reusable UI components"),
+ li("**Progressive Web Apps (PWAs)** that bridge the gap between web and native applications"),
+ li("**Serverless architectures** using AWS Lambda, Vercel Functions, and Netlify Functions"),
+ li("**JAMstack** (JavaScript, APIs, Markup) for better performance and security"),
+ li("**GraphQL** for more efficient data fetching and API design"),
+ li("**TypeScript** for type-safe JavaScript development"),
+ li("**Edge computing** for reduced latency and improved user experience")
+ ),
+ h3("Framework Comparison"),
+ table({"class": "stats-table"},
+ thead(
+ tr(
+ th("Framework"),
+ th("Learning Curve"),
+ th("Performance"),
+ th("Community"),
+ th("Use Case")
+ )
),
+ tbody(
+ tr(
+ td("React"),
+ td("Medium"),
+ td("High"),
+ td("Very Large"),
+ td("Complex UIs, SPAs")
+ ),
+ tr(
+ td("Vue.js"),
+ td("Easy"),
+ td("High"),
+ td("Large"),
+ td("Rapid prototyping, SME apps")
+ ),
+ tr(
+ td("Angular"),
+ td("Steep"),
+ td("High"),
+ td("Large"),
+ td("Enterprise applications")
+ ),
+ tr(
+ td("Svelte"),
+ td("Easy"),
+ td("Very High"),
+ td("Growing"),
+ td("Performance-critical apps")
+ )
+ )
+ ),
+ h3("Code Example: Modern Component"),
+ p("Here's an example of a modern React component using hooks and TypeScript:"),
+ pre({"class": "code-block"},
+ code("""
+ interface User {
+ id: number;
+ name: string;
+ email: string;
+ }
+
+ const UserProfile: React.FC<{ userId: number }> = ({ userId }) => {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
- footer({"class": "site-footer"},
- div({"class": "container"},
- div({"class": "footer-content"},
- div({"class": "footer-section"},
- h4("About Tech Insights"),
- p("Your go-to resource for web development tutorials, programming guides, and the latest technology trends. We help developers stay current with industry best practices.")
- ),
- div({"class": "footer-section"},
- h4("Quick Links"),
- ul(
- li(a({"href": "/privacy"}, "Privacy Policy")),
- li(a({"href": "/terms"}, "Terms of Service")),
- li(a({"href": "/sitemap"}, "Sitemap")),
- li(a({"href": "/advertise"}, "Advertise"))
- )
- ),
- div({"class": "footer-section"},
- h4("Contact Info"),
- p("Email: hello@techinsights.dev"),
- p("Location: San Francisco, CA"),
- p("Phone: (555) 123-4567")
- )
- ),
- hr,
- div({"class": "footer-bottom"},
- p("© 2024 Tech Insights. All rights reserved. Built with simple_html library."),
- p("Made with ❤️ for the developer community")
- )
- )
- )
- )
- )
+ useEffect(() => {
+ fetchUser(userId)
+ .then(setUser)
+ .finally(() => setLoading(false));
+ }, [userId]);
+
+ if (loading) return Loading...
;
+ if (!user) return User not found
;
+
+ return (
+
+
{user.name}
+
{user.email}
+
+ );
+ };
+ """)
+ ),
+ h3("Best Practices for 2024"),
+ p("As we move forward in 2024, several best practices have emerged:"),
+ ol(
+ li("**Performance First**: Optimize for Core Web Vitals and user experience metrics"),
+ li("**Accessibility by Default**: Implement WCAG guidelines from the start of development"),
+ li("**Security-First Mindset**: Use CSP headers, sanitize inputs, and follow OWASP guidelines"),
+ li("**Mobile-First Design**: Start with mobile layouts and progressively enhance for larger screens"),
+ li("**Sustainable Web Development**: Optimize for energy efficiency and reduced carbon footprint")
+ ),
+ blockquote(
+ p("\"The best web developers are those who understand that technology should serve users, not the other way around.\""),
+ footer("— John Doe, Senior Frontend Architect at TechCorp")
+ )]
+
+ ),
+ _article(
+ "The Rise of AI in Development: Tools and Techniques",
+ "2024-03-10",
+ "March 10, 2024",
+ "Michael Chen",
+ 8,
+ [p(
+ "Artificial Intelligence is fundamentally transforming how we write, test, and deploy code. ",
+ "From intelligent autocomplete suggestions to automated bug detection and code generation, ",
+ "AI tools are becoming essential companions for modern developers."
+ ),
+ h3("Popular AI Development Tools"),
+ ul(
+ li("**GitHub Copilot**: AI-powered code completion and generation"),
+ li("**ChatGPT & GPT-4**: Code explanation, debugging, and architecture advice"),
+ li("**Amazon CodeWhisperer**: Real-time code suggestions with security scanning"),
+ li("**DeepCode**: AI-powered code review and vulnerability detection"),
+ li("**Kite**: Intelligent code completion for Python and JavaScript")
+ ),
+ p(
+ "These tools don't replace developers but rather augment their capabilities, ",
+ "allowing them to focus on higher-level problem solving and creative solutions."
+ )
+ ]
+ ),
+
+ _article(
+ "Python vs JavaScript: Which Language to Learn in 2024?",
+ "2024-03-05",
+ "March 5, 2024",
+ "Emily Rodriguez",
+ 9,
+ [
+ p(
+ "The eternal debate continues: should new developers learn Python or JavaScript first? ",
+ "Both languages have their strengths and use cases, and the answer largely depends on ",
+ "your career goals and the type of projects you want to work on."
+ ),
+ h3("Python Advantages"),
+ ul(
+ li("Simple, readable syntax that's beginner-friendly"),
+ li("Excellent for data science, machine learning, and AI"),
+ li("Strong in automation, scripting, and backend development"),
+ li("Huge ecosystem of libraries and frameworks (Django, Flask, NumPy, pandas)")
+ ),
+ h3("JavaScript Advantages"),
+ ul(
+ li("Essential for web development (frontend and backend with Node.js)"),
+ li("Immediate visual feedback when learning"),
+ li("Huge job market and demand"),
+ li("Versatile: runs in browsers, servers, mobile apps, and desktop applications")
+ ),
+ p("The truth is, both languages are valuable, and learning one makes learning the other easier.")
+ ]
+ )]
+
+ render(
+ DOCTYPE_HTML5,
+ _html(t, articles)
)
diff --git a/poetry.lock b/poetry.lock
index e3cfa36..851d40b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "colorama"
@@ -88,121 +88,149 @@ i18n = ["Babel (>=2.7)"]
[[package]]
name = "markupsafe"
-version = "3.0.2"
+version = "3.0.3"
description = "Safely add untrusted strings to HTML/XML markup."
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
- {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
- {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
- {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
- {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
- {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
- {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
- {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
+ {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"},
+ {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"},
+ {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"},
+ {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"},
+ {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"},
+ {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"},
+ {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"},
+ {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"},
+ {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"},
+ {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"},
+ {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"},
+ {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"},
+ {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"},
+ {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"},
+ {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"},
+ {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"},
+ {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"},
+ {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"},
+ {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"},
+ {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"},
+ {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"},
+ {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"},
+ {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"},
+ {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"},
+ {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"},
+ {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"},
+ {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"},
+ {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"},
+ {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"},
+ {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"},
+ {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"},
+ {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"},
]
[[package]]
name = "mypy"
-version = "1.17.1"
+version = "1.18.2"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"},
- {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"},
- {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"},
- {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"},
- {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"},
- {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"},
- {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"},
- {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"},
- {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"},
- {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"},
- {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"},
- {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"},
- {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"},
- {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"},
- {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"},
- {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"},
- {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"},
- {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"},
- {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"},
- {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"},
- {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"},
- {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"},
- {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"},
- {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"},
- {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"},
- {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"},
- {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"},
- {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"},
- {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"},
- {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"},
- {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"},
- {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"},
- {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"},
- {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"},
- {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"},
- {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"},
- {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"},
- {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"},
+ {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"},
+ {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"},
+ {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"},
+ {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"},
+ {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"},
+ {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"},
+ {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"},
+ {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"},
+ {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"},
+ {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"},
+ {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"},
+ {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"},
+ {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"},
+ {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"},
+ {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"},
+ {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"},
+ {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"},
+ {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"},
+ {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"},
+ {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"},
+ {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"},
+ {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"},
+ {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"},
+ {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"},
+ {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"},
+ {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"},
+ {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"},
+ {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"},
+ {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"},
+ {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"},
+ {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"},
+ {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"},
+ {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"},
+ {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"},
+ {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"},
+ {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"},
+ {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"},
+ {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"},
]
[package.dependencies]
@@ -360,60 +388,70 @@ type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.deve
[[package]]
name = "tomli"
-version = "2.2.1"
+version = "2.3.0"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
markers = "python_version < \"3.11\""
files = [
- {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
- {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
- {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
- {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
- {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
- {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
- {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
- {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
- {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
- {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
- {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
- {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
- {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
- {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
- {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
- {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
- {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
- {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
+ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"},
+ {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"},
+ {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"},
+ {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"},
+ {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"},
+ {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"},
+ {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"},
+ {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"},
+ {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"},
+ {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"},
+ {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"},
+ {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"},
+ {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"},
+ {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"},
+ {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"},
+ {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"},
+ {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"},
+ {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"},
+ {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"},
+ {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"},
+ {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"},
+ {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"},
+ {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"},
+ {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"},
+ {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"},
+ {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"},
+ {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"},
+ {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"},
+ {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"},
+ {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"},
+ {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"},
+ {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"},
+ {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"},
+ {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"},
+ {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"},
+ {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"},
+ {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"},
+ {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"},
+ {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"},
+ {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"},
+ {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"},
+ {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"},
]
[[package]]
name = "typing-extensions"
-version = "4.14.1"
+version = "4.15.0"
description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"},
- {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"},
+ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"},
+ {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"},
]
[metadata]
lock-version = "2.1"
python-versions = "^3.9"
-content-hash = "f13d075b341576d6520c0808e3bcb76e63c106297f18e79eb53c33b0b92e2cca"
+content-hash = "aa1e0fc6567208e34764ca9f38470d70d3ca432428676c6f06c84024fee2c167"
diff --git a/pyproject.toml b/pyproject.toml
index 17c857f..9858ff3 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "simple-html"
-version = "3.0.0"
+version = "3.1.0"
description="Template-less HTML rendering in Python"
authors = [
{name="Keith Philpott"}
@@ -34,7 +34,7 @@ python = "^3.9"
[tool.poetry.group.dev.dependencies]
jinja2 = "3.1.6"
-mypy = "1.17.1"
+mypy = "1.18.2"
pytest = "8.4.1"
setuptools = "80.9.0"
fast-html = "1.0.12"
@@ -45,7 +45,7 @@ ruff = "0.12.8"
[build-system]
requires = [
"poetry-core>=1.0.0",
- "mypy[mypyc]==1.17.1",
+ "mypy[mypyc]==1.18.2",
"tomli==2.2.1"
]
@@ -76,7 +76,7 @@ warn_unused_ignores = true
warn_unreachable = true
[tool.cibuildwheel]
-skip = ["cp38-*", "cp314*"]
+skip = ["cp38-*", "cp315*"]
[tool.cibuildwheel.windows]
archs = ["AMD64", "x86"]
diff --git a/setup.py b/setup.py
index 9d74519..e7c59b5 100644
--- a/setup.py
+++ b/setup.py
@@ -2,7 +2,7 @@
import tomllib
except ModuleNotFoundError:
# python 3.10 and earlier
- import tomli as tomllib
+ import tomli as tomllib # type: ignore[no-redef]
from pathlib import Path
from setuptools import setup
@@ -19,7 +19,7 @@
setup(
name="simple-html",
ext_modules=mypycify([
- "simple_html/utils.py",
+ "simple_html",
]),
author=project_data["authors"][0]["name"],
packages=["simple_html"],
diff --git a/simple_html/__init__.py b/simple_html/__init__.py
index 4f323bd..6a743a0 100644
--- a/simple_html/__init__.py
+++ b/simple_html/__init__.py
@@ -1,4 +1,4 @@
-from simple_html.utils import SafeString as SafeString, Tag, render as render, render_styles as render_styles, Node as Node, TagTuple as TagTuple
+from simple_html.core import SafeString as SafeString, Tag as Tag, render as render, render_styles as render_styles, Node as Node, TagTuple as TagTuple, prerender as prerender
DOCTYPE_HTML5 = SafeString("")
diff --git a/simple_html/utils.py b/simple_html/core.py
similarity index 96%
rename from simple_html/utils.py
rename to simple_html/core.py
index ed886e5..183d775 100644
--- a/simple_html/utils.py
+++ b/simple_html/core.py
@@ -4,6 +4,8 @@
class SafeString:
+ __slots__ = ("safe_str",)
+
def __init__(self, safe_str: str) -> None:
self.safe_str = safe_str
@@ -24,6 +26,9 @@ def faster_escape(s: str) -> str:
- we don't check if some of the replacements are desired
- we don't re-assign a variable many times.
"""
+ if "'" not in s and '"' not in s and '<' not in s and ">" not in s and '&' not in s:
+ return s
+
return s.replace(
"&", "&" # Must be done first!
).replace("<", "<").replace(">", ">").replace('"', """).replace('\'', "'")
@@ -177,14 +182,16 @@ def _render(nodes: Iterable[Node], append_to_list: Callable[[str], None]) -> Non
mutate a list instead of constantly rendering strings
"""
for node in nodes:
- if type(node) is tuple:
- append_to_list(node[0])
- _render(node[1], append_to_list)
- append_to_list(node[2])
- elif type(node) is SafeString:
+ # SafeString first because they are very common in performance-sensitive contexts,
+ # such as `prerender`
+ if type(node) is SafeString:
append_to_list(node.safe_str)
elif type(node) is str:
append_to_list(faster_escape(node))
+ elif type(node) is tuple:
+ append_to_list(node[0])
+ _render(node[1], append_to_list)
+ append_to_list(node[2])
elif type(node) is Tag:
append_to_list(node.rendered)
elif type(node) is list or type(node) is GeneratorType:
@@ -437,3 +444,7 @@ def render(*nodes: Node) -> str:
_render(nodes, results.append)
return "".join(results)
+
+
+def prerender(*nodes: Node) -> SafeString:
+ return SafeString(render(*nodes))
diff --git a/tests/test_simple_html.py b/tests/test_core.py
similarity index 98%
rename from tests/test_simple_html.py
rename to tests/test_core.py
index bda13af..0e23e85 100644
--- a/tests/test_simple_html.py
+++ b/tests/test_core.py
@@ -2,6 +2,7 @@
from decimal import Decimal
from typing import Generator
+
from simple_html import (
SafeString,
a,
@@ -23,7 +24,7 @@
render_styles,
img,
)
-from simple_html.utils import escape_attribute_key
+from simple_html.core import escape_attribute_key
def test_renders_no_children() -> None:
@@ -266,4 +267,4 @@ def test_tag_repr() -> None:
assert repr(img) == "Tag(name='img', self_closing=True)"
def test_render_number_attributes() -> None:
- assert render(div({"x": 1, "y": 2.01, "z": Decimal("3.02")})) == ''
\ No newline at end of file
+ assert render(div({"x": 1, "y": 2.01, "z": Decimal("3.02")})) == ''