Skip to main content

Python interface to generate executable Typst code.

Project description

typstpy

Introduction

typstpy is a python package for generating executable typst codes. This package is written primarily in functional programming paradigm with some OOP contents. Each module in this package has greater than 90% unit test coverage.

This package provides interfaces which are as close as possible to typst's native functions. Through typstpy and other data processing packages, you can generate data report instantly.

Repository on GitHub: python-typst. Homepage on PyPI: python-typst. Any contributions are welcome.

Installation

pip install typstpy

Current Supports

Package's function name Typst's function name Documentation on typst
align align https://typst.app/docs/reference/layout/align/
bibliography bibliography https://typst.app/docs/reference/model/bibliography/
block block https://typst.app/docs/reference/layout/block/
box box https://typst.app/docs/reference/layout/box/
bullet_list list https://typst.app/docs/reference/model/list/
circle circle https://typst.app/docs/reference/visualize/circle/
cite cite https://typst.app/docs/reference/model/cite/
cmyk cmyk https://typst.app/docs/reference/visualize/color/#definitions-cmyk
colbreak colbreak https://typst.app/docs/reference/layout/colbreak/
color color https://typst.app/docs/reference/visualize/color/
columns columns https://typst.app/docs/reference/layout/columns/
document document https://typst.app/docs/reference/model/document/
ellipse ellipse https://typst.app/docs/reference/visualize/ellipse/
emph emph https://typst.app/docs/reference/model/emph/
figure figure https://typst.app/docs/reference/model/figure/
footnote footnote https://typst.app/docs/reference/model/footnote/
gradient gradient https://typst.app/docs/reference/visualize/gradient/
grid grid https://typst.app/docs/reference/layout/grid/
heading heading https://typst.app/docs/reference/model/heading/
hide hide https://typst.app/docs/reference/layout/hide/
highlight highlight https://typst.app/docs/reference/text/highlight/
hspace h https://typst.app/docs/reference/layout/h/
image image https://typst.app/docs/reference/visualize/image/
layout layout https://typst.app/docs/reference/layout/layout/
line line https://typst.app/docs/reference/visualize/line/
linebreak linebreak https://typst.app/docs/reference/text/linebreak/
link link https://typst.app/docs/reference/model/link/
lorem lorem https://typst.app/docs/reference/text/lorem/
lower lower https://typst.app/docs/reference/text/lower/
luma luma https://typst.app/docs/reference/visualize/color/#definitions-luma
measure measure https://typst.app/docs/reference/layout/measure/
move move https://typst.app/docs/reference/layout/move/
numbered_list enum https://typst.app/docs/reference/model/enum/
numbering numbering https://typst.app/docs/reference/model/numbering/
oklab oklab https://typst.app/docs/reference/visualize/color/#definitions-oklab
oklch oklch https://typst.app/docs/reference/visualize/color/#definitions-oklch
outline outline https://typst.app/docs/reference/model/outline/
overline overline https://typst.app/docs/reference/text/overline/
pad pad https://typst.app/docs/reference/layout/pad/
page page https://typst.app/docs/reference/layout/page/
pagebreak pagebreak https://typst.app/docs/reference/layout/pagebreak/
par par https://typst.app/docs/reference/model/par/
parbreak parbreak https://typst.app/docs/reference/model/parbreak/
path path https://typst.app/docs/reference/visualize/path/
pattern pattern https://typst.app/docs/reference/visualize/pattern/
place place https://typst.app/docs/reference/layout/place/
polygon polygon https://typst.app/docs/reference/visualize/polygon/
quote quote https://typst.app/docs/reference/model/quote/
raw raw https://typst.app/docs/reference/text/raw/
rect rect https://typst.app/docs/reference/visualize/rect/
ref ref https://typst.app/docs/reference/model/ref/
repeat repeat https://typst.app/docs/reference/layout/repeat/
rgb rgb https://typst.app/docs/reference/visualize/color/#definitions-rgb
rotate rotate https://typst.app/docs/reference/layout/rotate/
scale scale https://typst.app/docs/reference/layout/scale/
skew skew https://typst.app/docs/reference/layout/skew/
smallcaps smallcaps https://typst.app/docs/reference/text/smallcaps/
smartquote smartquote https://typst.app/docs/reference/text/smartquote/
square square https://typst.app/docs/reference/visualize/square/
stack stack https://typst.app/docs/reference/layout/stack/
strike strike https://typst.app/docs/reference/text/strike/
strong strong https://typst.app/docs/reference/model/strong/
subscript sub https://typst.app/docs/reference/text/sub/
superscript super https://typst.app/docs/reference/text/super/
table table https://typst.app/docs/reference/model/table/
terms terms https://typst.app/docs/reference/model/terms/
text text https://typst.app/docs/reference/text/text/
underline underline https://typst.app/docs/reference/text/underline/
upper upper https://typst.app/docs/reference/text/upper/
vspace v https://typst.app/docs/reference/layout/v/

Change logs

  • 1.0.3:
    • Fix: Fix the behavior of show_.
    • Compatibility: The parameters' order of show_ is flipped compared to previous version.
  • 1.0.2: Improved type annotations.
  • 1.0.1: Implement set, show, and import.
  • 1.0.0: Completed documentation and test cases in layout, model, text and visualize modules. Improved functionality.
  • 1.0.0-beta.2: Improved the implementation and documentation of functions in the layout module.
  • 1.0.0-beta.1: Completely reconstructed the underlying implementation.

Examples

from typstpy.std import *

align:

>>> align('"Hello, World!"', 'center')
'#align("Hello, World!", center)'
>>> align('[Hello, World!]', 'center')
'#align([Hello, World!], center)'
>>> align(lorem(20), 'center')
'#align(lorem(20), center)'

bibliography:

>>> bibliography('"bibliography.bib"', style='"cell"')
'#bibliography("bibliography.bib", style: "cell")'

block:

>>> block('"Hello, World!"')
'#block("Hello, World!")'
>>> block('[Hello, World!]')
'#block([Hello, World!])'
>>> block(lorem(20))
'#block(lorem(20))'
>>> block(lorem(20), width='100%')
'#block(lorem(20), width: 100%)'

box:

>>> box('"Hello, World!"')
'#box("Hello, World!")'
>>> box('[Hello, World!]')
'#box([Hello, World!])'
>>> box(lorem(20))
'#box(lorem(20))'
>>> box(lorem(20), width='100%')
'#box(lorem(20), width: 100%)'

bullet_list:

>>> bullet_list(lorem(20), lorem(20), lorem(20))
'#list(lorem(20), lorem(20), lorem(20))'
>>> bullet_list(lorem(20), lorem(20), lorem(20), tight=False)
'#list(tight: false, lorem(20), lorem(20), lorem(20))'

circle:

>>> circle('[Hello, world!]')
'#circle([Hello, world!])'
>>> circle('[Hello, world!]', radius='10pt')
'#circle([Hello, world!], radius: 10pt)'
>>> circle('[Hello, world!]', width='100%', height='100%')
'#circle([Hello, world!], width: 100%, height: 100%)'

cite:

>>> cite('<label>')
'#cite(<label>)'
>>> cite('<label>', supplement='[Hello, World!]')
'#cite(<label>, supplement: [Hello, World!])'
>>> cite('<label>', form='"prose"')
'#cite(<label>, form: "prose")'
>>> cite('<label>', style='"annual-reviews"')
'#cite(<label>, style: "annual-reviews")'

cmyk:

>>> cmyk('0%', '0%', '0%', '0%')
'#cmyk(0%, 0%, 0%, 0%)'
>>> cmyk('50%', '50%', '50%', '50%')
'#cmyk(50%, 50%, 50%, 50%)'

colbreak:

>>> colbreak()
'#colbreak()'
>>> colbreak(weak=True)
'#colbreak(weak: true)'

color:

>>> color()
'#color'

columns:

>>> columns(lorem(20))
'#columns(lorem(20))'
>>> columns(lorem(20), 3)
'#columns(lorem(20), 3)'
>>> columns(lorem(20), 3, gutter='8% + 0pt')
'#columns(lorem(20), 3, gutter: 8% + 0pt)'

ellipse:

>>> ellipse('[Hello, World!]')
'#ellipse([Hello, World!])'
>>> ellipse('[Hello, World!]', width='100%')
'#ellipse([Hello, World!], width: 100%)'

emph:

>>> emph('"Hello, World!"')
'#emph("Hello, World!")'
>>> emph('[Hello, World!]')
'#emph([Hello, World!])'

figure:

>>> figure(image('"image.png"'))
'#figure(image("image.png"))'
>>> figure(image('"image.png"'), caption='[Hello, World!]')
'#figure(image("image.png"), caption: [Hello, World!])'

footnote:

>>> footnote('[Hello, World!]')
'#footnote([Hello, World!])'
>>> footnote('[Hello, World!]', numbering='"a"')
'#footnote([Hello, World!], numbering: "a")'

gradient:

>>> gradient()
'#gradient'

grid:

>>> grid(lorem(20), lorem(20), lorem(20), align=('center',) * 3)
'#grid(align: (center, center, center), lorem(20), lorem(20), lorem(20))'

heading:

>>> heading('[Hello, World!]')
'#heading([Hello, World!])'
>>> heading('[Hello, World!]', level=1)
'#heading([Hello, World!], level: 1)'
>>> heading('[Hello, World!]', level=1, depth=2)
'#heading([Hello, World!], level: 1, depth: 2)'
>>> heading('[Hello, World!]', level=1, depth=2, offset=10)
'#heading([Hello, World!], level: 1, depth: 2, offset: 10)'
>>> heading('[Hello, World!]', level=1, depth=2, offset=10, numbering='"a"')
'#heading([Hello, World!], level: 1, depth: 2, offset: 10, numbering: "a")'
>>> heading(
...     '[Hello, World!]',
...     level=1,
...     depth=2,
...     offset=10,
...     numbering='"a"',
...     supplement='"Supplement"',
... )
'#heading([Hello, World!], level: 1, depth: 2, offset: 10, numbering: "a", supplement: "Supplement")'

hide:

>>> hide(lorem(20))
'#hide(lorem(20))'

highlight:

>>> highlight('"Hello, world!"', fill=rgb('"#ffffff"'))
'#highlight("Hello, world!", fill: rgb("#ffffff"))'
>>> highlight('"Hello, world!"', fill=rgb('"#ffffff"'), stroke=rgb('"#000000"'))
'#highlight("Hello, world!", fill: rgb("#ffffff"), stroke: rgb("#000000"))'
>>> highlight(
...     '"Hello, world!"',
...     fill=rgb('"#ffffff"'),
...     stroke=rgb('"#000000"'),
...     top_edge='"bounds"',
...     bottom_edge='"bounds"',
... )
'#highlight("Hello, world!", fill: rgb("#ffffff"), stroke: rgb("#000000"), top-edge: "bounds", bottom-edge: "bounds")'

hspace:

>>> hspace('1em')
'#h(1em)'
>>> hspace('1em', weak=True)
'#h(1em, weak: true)'

image:

>>> image('"image.png"')
'#image("image.png")'
>>> image('"image.png"', fit='"contain"')
'#image("image.png", fit: "contain")'

line:

>>> line()
'#line()'
>>> line(end=('100% + 0pt', '100% + 0pt'))
'#line(end: (100% + 0pt, 100% + 0pt))'
>>> line(angle='90deg')
'#line(angle: 90deg)'
>>> line(stroke='1pt + red')
'#line(stroke: 1pt + red)'

linebreak:

>>> linebreak()
'#linebreak()'
>>> linebreak(justify=True)
'#linebreak(justify: true)'

link:

>>> link('"https://typst.app"')
'#link("https://typst.app")'
>>> link('"https://typst.app"', '"Typst"')
'#link("https://typst.app", "Typst")'

lorem:

>>> lorem(10)
'#lorem(10)'

lower:

>>> lower('"Hello, World!"')
'#lower("Hello, World!")'
>>> lower('[Hello, World!]')
'#lower([Hello, World!])'
>>> lower(upper('"Hello, World!"'))
'#lower(upper("Hello, World!"))'

luma:

>>> luma('50%')
'#luma(50%)'
>>> luma('50%', '50%')
'#luma(50%, 50%)'

move:

>>> move(lorem(20), dx='50% + 10pt', dy='10% + 5pt')
'#move(lorem(20), dx: 50% + 10pt, dy: 10% + 5pt)'

numbered_list:

>>> numbered_list(lorem(20), lorem(20), lorem(20))
'#enum(lorem(20), lorem(20), lorem(20))'
>>> numbered_list(lorem(20), lorem(20), lorem(20), tight=False)
'#enum(tight: false, lorem(20), lorem(20), lorem(20))'

numbering:

>>> numbering('"1.1)"', 1, 2)
'#numbering("1.1)", 1, 2)'

oklab:

>>> oklab('50%', '0%', '0%')
'#oklab(50%, 0%, 0%)'
>>> oklab('50%', '0%', '0%', '50%')
'#oklab(50%, 0%, 0%, 50%)'

oklch:

>>> oklch('50%', '0%', '0deg')
'#oklch(50%, 0%, 0deg)'
>>> oklch('50%', '0%', '0deg', '50%')
'#oklch(50%, 0%, 0deg, 50%)'

outline:

>>> outline()
'#outline()'
>>> outline(title='"Hello, World!"', target=heading.where(outlined=False))
'#outline(title: "Hello, World!", target: heading.where(outlined: false))'

overline:

>>> overline('"Hello, World!"')
'#overline("Hello, World!")'
>>> overline('[Hello, World!]')
'#overline([Hello, World!])'
>>> overline(
...     upper('"Hello, World!"'),
...     stroke='red',
...     offset='0pt',
...     extent='0pt',
...     evade=False,
...     background=True,
... )
'#overline(upper("Hello, World!"), stroke: red, offset: 0pt, evade: false, background: true)'

pad:

>>> pad(
...     lorem(20),
...     left='4% + 0pt',
...     top='4% + 0pt',
...     right='4% + 0pt',
...     bottom='4% + 0pt',
... )
'#pad(lorem(20), left: 4% + 0pt, top: 4% + 0pt, right: 4% + 0pt, bottom: 4% + 0pt)'

page:

>>> page(lorem(20))
'#page(lorem(20))'
>>> page(lorem(20), paper='"a0"', width='8.5in', height='11in')
'#page(lorem(20), paper: "a0", width: 8.5in, height: 11in)'

pagebreak:

>>> pagebreak()
'#pagebreak()'
>>> pagebreak(weak=True)
'#pagebreak(weak: true)'
>>> pagebreak(to='"even"')
'#pagebreak(to: "even")'

par:

>>> par('"Hello, World!"')
'#par("Hello, World!")'
>>> par('[Hello, World!]')
'#par([Hello, World!])'
>>> par(
...     '[Hello, World!]',
...     leading='0.1em',
...     spacing='0.5em',
...     justify=True,
...     linebreaks='"simple"',
...     first_line_indent='0.2em',
...     hanging_indent='0.3em',
... )
'#par([Hello, World!], leading: 0.1em, spacing: 0.5em, justify: true, linebreaks: "simple", first-line-indent: 0.2em, hanging-indent: 0.3em)'

parbreak:

>>> parbreak()
'#parbreak()'

path:

>>> path(('0%', '0%'), ('100%', '0%'), ('100%', '100%'), ('0%', '100%'))
'#path((0%, 0%), (100%, 0%), (100%, 100%), (0%, 100%))'
>>> path(('0%', '0%'), ('100%', '0%'), ('100%', '100%'), ('0%', '100%'), fill='red')
'#path(fill: red, (0%, 0%), (100%, 0%), (100%, 100%), (0%, 100%))'
>>> path(
...     ('0%', '0%'),
...     ('100%', '0%'),
...     ('100%', '100%'),
...     ('0%', '100%'),
...     fill='red',
...     stroke='blue',
... )
'#path(fill: red, stroke: blue, (0%, 0%), (100%, 0%), (100%, 100%), (0%, 100%))'

place:

>>> place(lorem(20))
'#place(lorem(20))'
>>> place(lorem(20), 'top')
'#place(lorem(20), top)'

quote:

>>> quote('"Hello, World!"')
'#quote("Hello, World!")'
>>> quote('"Hello, World!"', block=True)
'#quote("Hello, World!", block: true)'
>>> quote('"Hello, World!"', quotes=False)
'#quote("Hello, World!", quotes: false)'
>>> quote('"Hello, World!"', attribution='"John Doe"')
'#quote("Hello, World!", attribution: "John Doe")'

raw:

>>> raw('"Hello, World!"')
'#raw("Hello, World!")'
>>> raw('"Hello, World!"', block=True, align='center')
'#raw("Hello, World!", block: true, align: center)'
>>> raw('"Hello, World!"', lang='"rust"')
'#raw("Hello, World!", lang: "rust")'
>>> raw('"Hello, World!"', tab_size=4)
'#raw("Hello, World!", tab-size: 4)'

ref:

>>> ref('<label>')
'#ref(<label>)'
>>> ref('<label>', supplement='[Hello, World!]')
'#ref(<label>, supplement: [Hello, World!])'

repeat:

>>> repeat(lorem(20), gap='0.5em')
'#repeat(lorem(20), gap: 0.5em)'
>>> repeat(lorem(20), gap='0.5em', justify=False)
'#repeat(lorem(20), gap: 0.5em, justify: false)'

rgb:

>>> rgb(255, 255, 255)
'#rgb(255, 255, 255)'
>>> rgb('50%', '50%', '50%', '50%')
'#rgb(50%, 50%, 50%, 50%)'
>>> rgb('"#ffffff"')
'#rgb("#ffffff")'

rotate:

>>> rotate(lorem(20), '20deg')
'#rotate(lorem(20), 20deg)'
>>> rotate(lorem(20), '20deg', origin='left + horizon')
'#rotate(lorem(20), 20deg, origin: left + horizon)'

scale:

>>> scale(lorem(20), '50%')
'#scale(lorem(20), 50%)'
>>> scale(lorem(20), x='50%', y='50%')
'#scale(lorem(20), x: 50%, y: 50%)'
>>> scale(lorem(20), '50%', x='50%', y='50%')
'#scale(lorem(20), 50%, x: 50%, y: 50%)'

skew:

>>> skew(lorem(20), ax='10deg', ay='20deg')
'#skew(lorem(20), ax: 10deg, ay: 20deg)'

smallcaps:

>>> smallcaps('"Hello, World!"')
'#smallcaps("Hello, World!")'
>>> smallcaps('[Hello, World!]')
'#smallcaps([Hello, World!])'

smartquote:

>>> smartquote(double=False, enabled=False, alternative=True, quotes='"()"')
'#smartquote(double: false, enabled: false, alternative: true, quotes: "()")'
>>> smartquote(quotes=('"()"', '"{}"'))
'#smartquote(quotes: ("()", "{}"))'

stack:

>>> stack(rect(width='40pt'), rect(width='120pt'), rect(width='90pt'), dir='btt')
'#stack(dir: btt, rect(width: 40pt), rect(width: 120pt), rect(width: 90pt))'
>>> stack((rect(width='40pt'), rect(width='120pt'), rect(width='90pt')), dir='btt')
'#stack(dir: btt, ..(rect(width: 40pt), rect(width: 120pt), rect(width: 90pt)))'

strike:

>>> strike('"Hello, World!"')
'#strike("Hello, World!")'
>>> strike('[Hello, World!]')
'#strike([Hello, World!])'
>>> strike(
...     upper('"Hello, World!"'),
...     stroke='red',
...     offset='0.1em',
...     extent='0.2em',
...     background=True,
... )
'#strike(upper("Hello, World!"), stroke: red, offset: 0.1em, extent: 0.2em, background: true)'

strong:

>>> strong('"Hello, World!"')
'#strong("Hello, World!")'
>>> strong('[Hello, World!]', delta=400)
'#strong([Hello, World!], delta: 400)'

subscript:

>>> subscript('"Hello, World!"')
'#sub("Hello, World!")'
>>> subscript('[Hello, World!]')
'#sub([Hello, World!])'
>>> subscript('[Hello, World!]', typographic=False, baseline='0.3em', size='0.7em')
'#sub([Hello, World!], typographic: false, baseline: 0.3em, size: 0.7em)'

superscript:

>>> superscript('"Hello, World!"')
'#super("Hello, World!")'
>>> superscript('[Hello, World!]')
'#super([Hello, World!])'
>>> superscript(
...     '[Hello, World!]', typographic=False, baseline='-0.4em', size='0.7em'
... )
'#super([Hello, World!], typographic: false, baseline: -0.4em, size: 0.7em)'

table:

>>> table('[1]', '[2]', '[3]')
'#table([1], [2], [3])'
>>> table(
...     '[1]',
...     '[2]',
...     '[3]',
...     columns=['1fr', '2fr', '3fr'],
...     rows=['1fr', '2fr', '3fr'],
...     gutter=['1fr', '2fr', '3fr'],
...     column_gutter=['1fr', '2fr', '3fr'],
...     row_gutter=['1fr', '2fr', '3fr'],
...     fill='red',
...     align=['center', 'center', 'center'],
... )
'#table(columns: (1fr, 2fr, 3fr), rows: (1fr, 2fr, 3fr), gutter: (1fr, 2fr, 3fr), column-gutter: (1fr, 2fr, 3fr), row-gutter: (1fr, 2fr, 3fr), fill: red, align: (center, center, center), [1], [2], [3])'

terms:

>>> terms(('[1]', lorem(20)), ('[1]', lorem(20)))
'#terms(([1], lorem(20)), ([1], lorem(20)))'
>>> terms(('[1]', lorem(20)), ('[1]', lorem(20)), tight=False)
'#terms(tight: false, ([1], lorem(20)), ([1], lorem(20)))'
>>> terms(terms.item('[1]', lorem(20)), terms.item('[1]', lorem(20)))
'#terms(terms.item([1], lorem(20)), terms.item([1], lorem(20)))'

text:

>>> text('"Hello, World!"')
'#text("Hello, World!")'
>>> text('[Hello, World!]')
'#text([Hello, World!])'
>>> text('[Hello, World!]', font='"Times New Roman"')
'#text([Hello, World!], font: "Times New Roman")'

underline:

>>> underline('"Hello, World!"')
'#underline("Hello, World!")'
>>> underline('[Hello, World!]')
'#underline([Hello, World!])'
>>> underline(
...     '[Hello, World!]',
...     stroke='1pt + red',
...     offset='0pt',
...     extent='1pt',
...     evade=False,
...     background=True,
... )
'#underline([Hello, World!], stroke: 1pt + red, offset: 0pt, extent: 1pt, evade: false, background: true)'

upper:

>>> upper('"Hello, World!"')
'#upper("Hello, World!")'
>>> upper('[Hello, World!]')
'#upper([Hello, World!])'
>>> upper(lower('"Hello, World!"'))
'#upper(lower("Hello, World!"))'

vspace:

>>> vspace('1em')
'#v(1em)'
>>> vspace('1em', weak=True)
'#v(1em, weak: true)'

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

typstpy-1.0.3.tar.gz (41.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

typstpy-1.0.3-py3-none-any.whl (40.0 kB view details)

Uploaded Python 3

File details

Details for the file typstpy-1.0.3.tar.gz.

File metadata

  • Download URL: typstpy-1.0.3.tar.gz
  • Upload date:
  • Size: 41.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.12.5 Windows/11

File hashes

Hashes for typstpy-1.0.3.tar.gz
Algorithm Hash digest
SHA256 28911f43d04ce5bcdd3a556b5500c74ff7064e05c6c557335b5b60876c9d46ba
MD5 a596248ae735779762b85762195f7212
BLAKE2b-256 937a83cbf1b867abd2d69a834c447807239ee3d6f5e822bac2c48a5c3dc45974

See more details on using hashes here.

File details

Details for the file typstpy-1.0.3-py3-none-any.whl.

File metadata

  • Download URL: typstpy-1.0.3-py3-none-any.whl
  • Upload date:
  • Size: 40.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.8.3 CPython/3.12.5 Windows/11

File hashes

Hashes for typstpy-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 9aa7d9209c0669ec757d9a5f00a8fc9e141d10fd5459b901cf8533bb5e01513c
MD5 f5ca3aaa02385332299c12c8f2b63b5b
BLAKE2b-256 74153bcf080b93109cbfb2aab495882cc69b3356a8a5da07c99861c61c0e6e2c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page